[英]Haskell `palindrome = reverse >>= (==)`
有人可以解释一下 function 中 Haskell 的回文是如何工作的:
palindrome :: Eq a => [a] -> Bool
palindrome = reverse >>= (==)
-- type declarations
reverse :: [a] -> [a]
>>= :: Monad m => m a -> (a -> m b) -> m b
(reverse >>=) :: ([a] -> [a] -> b) -> [a] -> b
(==) :: Eq a => a -> a -> Bool
特别是,函数上的 Monad 类型类的定义如何工作,以及它如何以某种方式将 (==) 的输入数量从两个列表减少到一个列表?
这有点令人兴奋,所以请注意:-)
monad 是一个类型构造函数,它接受一个类型参数。 例如, Maybe
就是这样一个类型构造函数。 所以, Maybe
是一个 monad,然后Maybe Int
是那个 monad 中的一个 monadic 值。 同样, IO
是一个单子,然后IO Int
是该单子中的单子值。
现在,也可以从具有两个类型参数的类型构造函数中创建一个 monad。 为此,我们只需要修复第一个。 例如,查看Either
:它有两个参数,但 monad 必须只有一个。 所以我们固定第一个参数。 因此, Either Bool
是一个 monad,然后Either Bool Int
是该 monad 中的一个 monadic 值。 类似地, Either String
是一个完全不同的 monad,然后Either String Int
是该 monad 中的一个 monadic 值。
另一方面,看看函数是如何表示的:
a -> b
但这只是中缀运算符的诡计,类似于5 + 42
或"foo" <> "bar"
。 这个中缀符号可以像这样“规范化”:
(->) a b
所以真的,“函数”可以看作是一个有两个类型参数的类型构造函数,就像Either
一样。 对于“函数”构造函数来说,这些参数具有特定的含义——一个是“函数输入”,另一个是“函数输出”。
好的,现在我们已经准备好将函数视为 monad。 就像Either
一样,为了做到这一点,我们必须修复第一个类型参数。 因此,例如, (->) Bool
是一个单子,然后(->) Bool Int
(也称为Bool -> Int
)是该单子中的单子值。 类似地, (->) String
是一个完全不同的 monad,然后(->) String Int
(也称为String -> Int
)是该 monad 中的一元值。
给你一个直觉,看待它的一种方式是,这种 monad 中的 monadic 值意味着“承诺”——即“你给我一个String
,我给你一个Int
返回”。 然后标准的一元组合让您可以将这些承诺组合在一起,就像您组合IO
动作一样。
跟我到现在? 好的,很好。
现在让我们看看如何实现绑定(又名>>=
)。 >>=
的签名如下:
(>>=) :: m a -> (a -> m b) -> m b
现在让我们将其专门用于函数。 现在,假设m ~ (->) Bool
。 然后我们有:
(>>=) :: (->) Bool a -> (a -> (->) Bool b) -> (->) Bool b
或者,如果我们以中缀形式重写(->)
构造函数,我们得到:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
所以你看 - 绑定接受“ a
的承诺”,然后是 function 从a
到“ b
的承诺”,然后返回“ b
的承诺”。 那么,实现是微不足道的:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
(>>=) promiseOfA f = \theBool -> f (promiseOfA theBool) theBool
这里我们创建一个新的“promise”,它是一个带Bool
的 function ,这个“promise”所做的是将给定的Bool
传递给“promise of a
”,然后将得到a
传递给 function f
,它返回 a “promise of b
”,然后我们将相同的Bool
传递给它,最终获得结果b
。
注意这里:看看theBool
是如何使用两次的——首先传递给promiseOfA
,然后再次传递给f
的结果? 这就是“减少参数数量”发生的地方。 这是我们两次传递参数的地方。
但是,当然,这不仅仅适用于Bool
s。 任何输入类型都是公平的游戏。 所以我们可以这样概括:
(>>=) :: (input -> a) -> (a -> (input -> b)) -> (input -> b)
(>>=) promiseOfA f = \i -> f (promiseOfA i) i
(与标准库中的实际定义进行比较)。
呸!
好的,现在我们终于准备好查看您的原始示例了。 首先,看reverse
。 由于它是 function,我们可以将其视为 monad (->) [a]
中的一元值 - 即“ [a]
的承诺”,其中“输入”也是[a]
。
然后, (==)
的签名:
(==) :: Eq x => x -> x -> Bool
(请注意,我故意将a
替换为x
:不要将其与a
from reverse
的签名混淆 - 它们是两个不同a
,不必是相同的类型)
我们可以将此签名视为一个 function ,它接受一个x
并返回另一个x -> Bool
类型的 function 。 所以:
(==) :: x -> (x -> Bool)
这可以看作是 bind 的第二个参数,即a -> mb
类型的参数。 为了这样看,我们需要说a ~ x
和m ~ (->) x
和b ~ Bool
。 所以这里讨论的单子是(->) x
- 即输入类型为x
的“承诺”。
可是等等! 为了将此 function 绑定到reverse
,它们需要在同一个 monad 中! 这意味着x ~ [a]
。 这反过来意味着(==)
的类型被固定到:
(==) :: [a] -> ([a] -> Bool)
因此,当我们调用(>>=)
将其reverse
作为第一个参数并将(==)
作为第二个参数时,我们得到:
reverse >>= (==)
= \i -> (==) (reverse i) i -- by my definition above
= \i -> reverse i == i
我认为这将是对我之前的答案的扩展,我在其中解释了 function 如何成为仿函数和应用程序。 让我从同一句话开始。
我们可以将函数( (->) r
)视为具有包含值的上下文,一旦应用就会显示出来。 在这里,我们有reverse
function ,其中包含一个反向列表,但直到我们将其应用于列表时才得到它。 让我们记住 bind 的类型签名。
>>= :: Monad m => m a -> (a -> m b) -> m b
这里ma
是reverse
function 而m
里面的a
是反向列表。 (==)
是(a -> mb)
并且mb
是a -> Bool
。 所以>>=
返回一个mb
,它只是一个 function ,它接受一个列表并返回一个Bool
。 这是一致的,因为我们在 function monad 中,所以它需要一个 monad(函数)并返回另一个。 只有当我们用一个列表调用返回的那个时,整个机制才会运行。 ma
和mb
都应用于提供的列表。
手动实现(->) r
的单子实例就像
return x = \_ -> x -- aka const
f >>= g = \r -> g (f r) r
如果我们仔细研究,我们会注意到>>=
实际上是<*>
的翻转版本,用于(->) r
类型。 但是当我说翻转它并不意味着g <*> f
== f >>= g
时要小心。
g <*> f = \x -> g x (f x)
f >>= g = \x -> g (f x) x
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.