import Control.Applicative
import Control.Arrow
filter ((&&) <$> (>2) <*> (<7)) [1..10]
filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10]
两者得到相同的结果!然而,这对我来说很难理解。有人能详细解释一下吗?
最佳答案
让我们从第二个开始,它更简单。我们这里有两个神秘的运算符,其类型如下:
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
(>>>) :: Category cat => cat a b -> cat b c -> cat a c
Arrow
和Category
类型类主要涉及行为类似于函数的事物,其中当然包括函数本身,这里的两个实例都是简单的 (->)
。因此,重写类型以使用它:
(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
第二个与 (.)
的类型非常相似,熟悉的函数复合运算符;事实上,它们是相同的,只是参数交换了。第一个比较陌生,但类型再次告诉您需要知道的所有内容 - 它需要两个函数,两个函数都采用公共(public)类型的参数,并生成一个函数,该函数将两个函数的结果组合成一个元组。
因此,表达式 (>2) &&& (<7)
接受一个数字并生成一对 Bool
基于比较的值。然后将结果输入 uncurry (&&)
,只需要一对 Bool
s 并将它们组合在一起。生成的谓词用于以通常的方式过滤列表。
第一个比较神秘。我们有两个神秘的运算符,它们具有以下类型:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
观察 (<$>)
的第二个参数在这种情况下是 (>2)
,其类型为 (Ord a, Num a) => a -> Bool
,而 (<$>)
的类型的参数类型为 f a
。它们如何兼容?
答案是,就像我们可以替换(->)
一样对于 a
和cat
在早期的类型签名中,我们可以想到 a -> Bool
如(->) a Bool
,并替换((->) a)
对于f
。因此,使用((->) t)
重写类型。而是为了避免与其他类型变量 a
发生冲突:
(<$>) :: (a -> b) -> ((->) t) a -> ((->) t) b
(<*>) :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b
现在,将内容恢复为正常的中缀形式:
(<$>) :: (a -> b) -> (t -> a) -> (t -> b)
(<*>) :: (t -> (a -> b)) -> (t -> a) -> (t -> b)
第一个结果是函数组合,正如您可以从类型中观察到的那样。第二个更复杂,但类型再次告诉您需要什么 - 它需要两个带有公共(public)类型参数的函数,一个生成一个函数,另一个生成一个传递给该函数的参数。换句话说,类似 \f g x -> f x (g x)
。 (这个函数也恰好在组合逻辑中被称为 S combinator,这是逻辑学家 Haskell Curry 广泛探索的一个主题,其名字无疑看起来很奇怪!)
(<$>)
的组合和(<*>)
有点“扩展”什么(<$>)
单独执行,在这种情况下意味着采用一个带有两个参数的函数,两个具有公共(public)参数类型的函数,将单个值应用于后两个,然后将第一个函数应用于两个结果。所以((&&) <$> (>2) <*> (<7)) x
简化为(&&) ((>2) x) ((<7) x)
,或使用正常的中缀样式, x > 2 && x < 7
。和以前一样,复合表达式用于以通常的方式过滤列表。
另外,请注意,虽然这两个函数在某种程度上都被混淆了,但一旦您习惯了所使用的运算符,它们实际上就很容易阅读。第一个抽象了对单个参数执行多项操作的复合表达式,而第二个是将事物与函数组合串在一起的标准“管道”样式的通用形式。
我个人实际上发现第一个完全可读。但我不希望大多数人同意!
关于haskell - 您介意在论坛中解释一下代码吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6287785/