List 嵌套函数应用程序
定义这样的运算符很容易List 嵌套函数应用程序,list,function,haskell,List,Function,Haskell,定义这样的运算符很容易 (@) :: [x -> y] -> [x] -> [y] 它获取函数列表和输入列表,并返回输出列表。有两种明显的方法可以实现这一点: 将第一个函数应用于第一个输入,将第二个函数应用于第二个输入,以此类推 将每个函数应用于每个输入 两者都是同样微不足道的定义。它的好处是你现在可以做类似的事情 foo :: X -> Y -> Z -> R bar :: [X] -> [Y] -> [Z] -> [R] bar xs
(@) :: [x -> y] -> [x] -> [y]
它获取函数列表和输入列表,并返回输出列表。有两种明显的方法可以实现这一点:
foo :: X -> Y -> Z -> R
bar :: [X] -> [Y] -> [Z] -> [R]
bar xs ys zs = [foo] @@ xs @@ ys @@ zs
这可以推广到任意数量的函数参数
到目前为止还不错。现在来看问题:如何更改
@
的类型签名,使条
的类型签名变为
bar :: [X] -> [Y] -> [Z] -> [[[R]]]
实现这种类型的函数并不难;这两种方法都可以:
bar xs ys zs = map (\ x -> map (\ y -> map (\ z -> foo x y z) zs) ys) zs
bar xs ys zs = map (\ z -> map (\ y -> map (\ x -> foo x y z) xs) ys) zs
我对我得到的结果并不挑剔。但是我不知道如何调整@
操作符来实现这一点
一个显而易见的尝试是
(@@) :: [x -> y] -> [x] -> [[y]]
实现这一点并不难,但对您没有帮助。现在你有了
[foo] @@ xs :: [[Y -> Z -> R]]
它不是@
的有效输入。没有明显的方法知道要达到多少级别的列表才能到达函数;显然,这种方法是一条死胡同
我尝试了其他几种可能的类型签名,但没有一种能让我更接近答案。有人能给我一个解决方案,或者解释一下为什么没有解决方案吗?你已经想到了为什么这很麻烦。您的函数
(@@)
应用于不同类型的输入(例如[x->y]
,[[x->y]]
,等等。这意味着您的@
类型签名的限制性太强了;您需要添加一些多态性,使其更通用,以便将其用于嵌套列表。由于Haskell对类型类实现多态性,这是一个很好的尝试方向
碰巧的是,如果你知道LHS的类型,你就可以唯一地确定RHS和结果。当输入类型为[a->b]
时,RHS必须是[a]
,结果必须是[[b]]
。这可以简化为a->b
的输入,RHS必须是[a]
,以及[b]
的结果。由于LHS确定了其他参数和结果,因此我们可以使用fundeps或类型族来表示其他类型
{-# LANGUAGE TypeFamilies, UndecidableInstances #-}
class Apply inp where
type Left inp :: *
type Result inp :: *
(@@) :: inp -> [Left inp] -> [Result inp]
现在我们有了一个类型类,我们可以为函数创建一个明显的实例:
instance Apply (a -> b) where
type Left (a -> b) = a
type Result (a -> b) = b
(@@) = map
列表实例也不错:
instance Apply f => Apply [f] where
type Left [f] = Left f
type Result [f] = [Result f]
l @@ r = map (\f -> f @@ r) l
-- or map (@@ r) l
现在,我们的类方法@
应该可以处理任意深度的列表。下面是一些测试:
*Main> (+) @@ [1..3] @@ [10..13]
[[11,12,13,14],[12,13,14,15],[13,14,15,16]]'s
let foo :: Int -> Char -> Char -> String
foo a b c = b:c:show a
*Main> foo @@ [1,2] @@ "ab" @@ "de"
[[["ad1","ae1"],["bd1","be1"]],[["ad2","ae2"],["bd2","be2"]]]
您可能想看看printf的printf
实现以获得进一步的启发
编辑:发布此消息后不久,我意识到可以将我的
Apply
类中的容器类型从List
概括为Applicative
,然后使用Applicative实例而不是map。这将允许常规List和ZipList
行为。实际上,这不需要类型类完全是错误的!避免类型类会让您失去一点便利,但仅此而已
关键的一点是,尽管重用了一个组合符,多态性允许每次使用的类型不同。这与Applicative
-风格表达式(如f xs ys zs
)背后的原理相同,这里的最终结果看起来很相似。因此,我们将对任何Functor
,而不仅仅是列表
这与Applicative
版本的不同之处在于,我们在每一步中都会深入到嵌套的Functor
中。必要的多态性要求在最内层具有灵活性,因此为了实现这一点,我们将使用一种延续ish技巧,其中每个组合符的结果都是一个接受转换的函数要在最内层使用的信息
我们需要两个操作员,一个启动链,另一个以增量方式继续。从后者开始:
(@@) :: (Functor f) => (((a -> b) -> f c) -> r) -> f a -> (b -> c) -> r
q @@ xs = \k -> q (\f -> k . f <$> xs)
链是通过简单地映射第一个参数上的所有其他参数来启动的
与更简单的Applicative
情况不同,我们还需要显式结束该链。与Cont
monad一样,最简单的方法是将结果应用于identity函数。我们将给出一个有用的名称:
nested = ($ id)
现在,我们可以这样做:
test2 :: [X -> Y -> R] -> [X] -> [Y] -> [[[R]]]
test2 fs xs ys = nested (fs <@ xs @@ ys)
test3 :: [X -> Y -> Z -> R] -> [X] -> [Y] -> [Z] -> [[[[R]]]]
test3 fs xs ys zs = nested (fs <@ xs @@ ys @@ zs)
test2::[X->Y->R]->[X]->[Y]->[R]]
test2 fs xs ys=nested(fs Y->Z->R]->[X]->[Y]->[Z]->[[[R]]]]
test3 fs xs ys zs=nested(fs与问题无关,但是您的原始(@@)
(第二版)相当于列表的Applicative
实例。将每个函数应用于每个输入的第一个版本是ZipList
newtype的Applicative
实例。“each”和“each”之间有什么区别?@Prateek:difference-between[f x y|x@Prateek与zipWith($)
和ap
之间的区别相同。是的,我可以猜到这是实现(@)的唯一两种合理方法
。但是从单词“each”和“every”的意思来看,仍然不清楚。它们在这里的意思似乎是一样的。这实际上与问题的答案非常相似,正如OP所指出的那样。printf
。其他有趣的测试:[negate,id]@[1..3]
和[(+),()][1..3]@[11..13]
这是一个很好的彻底的答案……但我希望有某种方法可以避免需要typeclass。最终,类在编译过程中被删除,所以一定有某种方法可以做到。我想这只是一个问题,它到底有多不方便……当然,看起来可能需要一个Oleg来解决这个问题o_O@MathematicalOrchid-即使不使用类型类,您仍然需要一种基于参数类型选择适当函数的方法。您可能可以通过Data.Typeable
手动模拟类型类词典,但这是最好的方法
test2 :: [X -> Y -> R] -> [X] -> [Y] -> [[[R]]]
test2 fs xs ys = nested (fs <@ xs @@ ys)
test3 :: [X -> Y -> Z -> R] -> [X] -> [Y] -> [Z] -> [[[[R]]]]
test3 fs xs ys zs = nested (fs <@ xs @@ ys @@ zs)