繁体   English   中英

Haskell`回文=反向>>=(==)`

[英]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 ~ xm ~ (->) xb ~ 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

这里mareverse function 而m里面的a是反向列表。 (==)(a -> mb)并且mba -> Bool 所以>>=返回一个mb ,它只是一个 function ,它接受一个列表并返回一个Bool 这是一致的,因为我们在 function monad 中,所以它需要一个 monad(函数)并返回另一个。 只有当我们用一个列表调用返回的那个时,整个机制才会运行。 mamb都应用于提供的列表。

手动实现(->) 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM