你介意在论坛中解释一下代码吗?

时间:2011-06-09 03:22:32

标签: haskell

import Control.Applicative
import Control.Arrow

filter ((&&) <$> (>2) <*> (<7)) [1..10]  
filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10]

两者都得到相同的结果!但是,我很难理解。有人可以在这里详细解释一下吗?

1 个答案:

答案 0 :(得分:23)

让我们从第二个开始,这更简单。我们这里有两个神秘的操作符,具有以下类型:

(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
(>>>) :: Category cat => cat a b -> cat b c -> cat a c

ArrowCategory类型类主要是关于行为类似函数的事物,当然包括函数本身,这里的两个实例都只是普通的(->)。所以,重写要使用的类型:

(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)

第二种类型与(.)非常相似,是熟悉的函数组合运算符;事实上,它们是相同的,只是交换了参数。第一个是比较陌生的,但类型再次告诉你所有你需要知道的东西 - 它需要两个函数,都采用一个普通类型的参数,并产生一个函数,将两者的结果组合成一个元组。 / p>

因此,表达式(>2) &&& (<7)只取一个数字,并根据比较产生一对Bool值。然后将结果输入uncurry (&&),它只需要一对Bool和AND它们在一起。结果谓词用于以通常的方式过滤列表。


第一个更神秘。我们还有两个神秘的运算符,它们具有以下类型:

(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

观察到(<$>)在这种情况下的第二个参数是(>2),其类型为(Ord a, Num a) => a -> Bool,而(<$>)的参数类型为{{1} }}。这些是如何兼容的?

答案是,正如我们可以在早期类型签名中替换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) 之类的东西。 (这个函数恰好也被称为组合逻辑中的S combinator ,这是一个由逻辑学家Haskell Curry广泛探讨的主题,其名称无疑似乎很奇怪!)

\f g x -> f x (g x)(<$>)的组合“扩展”(<*>)单独执行的操作,在这种情况下意味着使用具有两个参数的函数,两个函数具有公共参数类型,将单个值应用于后两个,然后将第一个函数应用于两个结果。因此(<$>)简化为((&&) <$> (>2) <*> (<7)) x,或使用普通中缀样式(&&) ((>2) x) ((<7) x)。和以前一样,复合表达式用于以通常的方式过滤列表。


另外,请注意,虽然这两个函数在某种程度上都是混淆的,但是一旦习惯了所使用的运算符,它们实际上就具有很强的可读性。第一个抽象的复合表达式对单个参数执行多个操作,而第二个抽象是标准“管道”样式的通用形式,将事物与函数组合串联起来。

我个人认为第一个完全可读。但我不希望大多数人同意!