Haskell `回文 = 反转 >>= (==)`
Haskell `palindrome = reverse >>= (==)`
谁能解释一下 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 类型类在函数上的定义是如何工作的,它如何以某种方式将 (==) 的输入数量从两个列表减少到一个列表?
我认为这将是 的扩展,我在其中解释了函数如何成为仿函数和应用函数。让我从同一句话开始。
我们可以将函数 ((->) r
) 视为上下文,其中包含的值一旦应用就会显示出来。这里我们有 reverse
函数,它包含一个反向列表,但是直到我们将它应用到列表中我们才得到它。让我们记住 bind 的类型签名。
>>= :: Monad m => m a -> (a -> m b) -> m b
这里m a
是reverse
函数,m
里面的a
是反转列表。 (==)
是 (a -> m b)
而 m b
是 a -> Bool
。所以 >>=
returns 一个 m b
这只是一个函数,它接受一个列表和 returns 一个 Bool
。这是一致的,因为我们在函数 monad 中,所以它需要一个 monad(函数)和 returns 另一个。只有当我们用一个列表调用返回的那个时,整个机制才会运行。 m a
和 m b
都应用于提供的列表。
手动实现 (->) r
的 monad 实例就像
return x = \_ -> x -- aka const
f >>= g = \r -> g (f r) r
如果仔细研究,我们会注意到 >>=
实际上是 (->) r
类型的 <*>
的翻转版本。但是当我说 flipped 时要小心,它并不意味着 g <*> f
== f >>= g
.
g <*> f = \x -> g x (f x)
f >>= g = \x -> g (f x) x
这有点令人兴奋,所以系好安全带:-)
monad 是一种接受一个类型参数的类型构造函数。例如,Maybe
就是这样一个类型构造函数。所以,Maybe
是一个 monad,然后 Maybe Int
是那个 monad 中的一个 monadic 值。类似地,IO
是一个 monad,然后 IO Int
是那个 monad 中的一个 monadic 值。
现在,也可以使用具有 两个 类型参数的类型构造函数来创建 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"
。这个中缀表示法可以是 "canonicalized" 这样的:
(->) a b
实际上,"function" 可以看作是一个具有两个类型参数的类型构造函数,就像 Either
一样。对于"function"构造函数来说,这些参数都有特定的含义——一个是"function input",另一个是"function output".
好的,现在我们准备好将函数视为 monad。就像 Either
一样,为了做到这一点,我们必须修复第一个类型参数。因此,例如,(->) Bool
是一个单子,然后 (->) Bool Int
(也称为 Bool -> Int
)是该单子中的一个单子值。类似地,(->) String
是一个 完全不同的 monad,然后 (->) String Int
(也称为 String -> Int
)是那个 monad 中的一个 monadic 值。
为了给你一个直觉,一种看待它的方式是这样的 monad 中的 monadic 值意味着 "promise" - 也就是说,“你给我一个 String
然后我给你一个 Int
回 ”。然后标准的 monadic 组合让您可以将这些承诺组合在一起,就像您编写 IO
动作一样。
到现在为止?好的,很好。
现在让我们看看如何实现绑定(又名 >>=
)。 >>=
的签名如下:
(>>=) :: m a -> (a -> m b) -> m b
现在让我们将其专门用于函数。假设现在 m ~ (->) Bool
。然后我们有:
(>>=) :: (->) Bool a -> (a -> (->) Bool b) -> (->) Bool b
或者,如果我们以中缀形式重写 (->)
构造函数,我们得到:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
所以你看 - 绑定采用 "promise of a
",然后是从 a
到 "promise of b
" 的函数,然后是 returns 和 "promise of b
"。那么,实现是微不足道的:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
(>>=) promiseOfA f = \theBool -> f (promiseOfA theBool) theBool
这里我们创建了一个新的 "promise",它是一个接受 Bool
的函数,这个 "promise" 所做的是将给定的 Bool
传递给 "promise of a
",然后将结果a
传递给函数f
,其中returns一个"promise of b
",我们再将相同的Bool
传递给函数,最终得到结果 b
.
这里注意一下:看看theBool
是如何被使用两次的——先传给promiseOfA
然后再传给f
的结果?这就是 "reducing of the number of arguments" 发生的地方。这是我们两次传递参数的地方。
当然,这不一定只适用于 Bool
s。任何输入类型都是公平的游戏。所以我们可以这样概括它:
(>>=) :: (input -> a) -> (a -> (input -> b)) -> (input -> b)
(>>=) promiseOfA f = \i -> f (promiseOfA i) i
(与the actual definition from the standard library比较)。
呸!
好的,现在我们终于可以查看您的原始示例了。首先,看reverse
。由于它是一个函数,我们可以将其视为 monad (->) [a]
中的 monadic 值 - 即 "promise of [a]
",其中 "input" 也是 [a]
.
然后,(==)
的签名:
(==) :: Eq x => x -> x -> Bool
(请注意,我故意将 a
替换为 x
:不要将其与 reverse
签名中的 a
混淆 - 它们是两个不同的 a
s,不必是同一类型)
我们可以将此签名视为一个接受 x
和 returns 另一个 x -> Bool
类型函数的函数。所以:
(==) :: x -> (x -> Bool)
这可以看作是 bind 的第二个参数,a -> m b
类型的参数。为了这样看,我们需要说 a ~ x
和 m ~ (->) x
和 b ~ Bool
。所以这里讨论的 monad 是 (->) x
- 即 "promise" 输入类型为 x
.
但是等等!为了将此函数绑定到 reverse
,它们需要在同一个 monad 中!这意味着 x ~ [a]
。这反过来意味着 (==)
的类型被固定为:
(==) :: [a] -> ([a] -> Bool)
因此,当我们调用 (>>=)
将其作为第一个参数传递 reverse
并将 (==)
作为第二个参数传递时,我们得到:
reverse >>= (==)
= \i -> (==) (reverse i) i -- by my definition above
= \i -> reverse i == i
谁能解释一下 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 类型类在函数上的定义是如何工作的,它如何以某种方式将 (==) 的输入数量从两个列表减少到一个列表?
我认为这将是
我们可以将函数 ((->) r
) 视为上下文,其中包含的值一旦应用就会显示出来。这里我们有 reverse
函数,它包含一个反向列表,但是直到我们将它应用到列表中我们才得到它。让我们记住 bind 的类型签名。
>>= :: Monad m => m a -> (a -> m b) -> m b
这里m a
是reverse
函数,m
里面的a
是反转列表。 (==)
是 (a -> m b)
而 m b
是 a -> Bool
。所以 >>=
returns 一个 m b
这只是一个函数,它接受一个列表和 returns 一个 Bool
。这是一致的,因为我们在函数 monad 中,所以它需要一个 monad(函数)和 returns 另一个。只有当我们用一个列表调用返回的那个时,整个机制才会运行。 m a
和 m b
都应用于提供的列表。
手动实现 (->) r
的 monad 实例就像
return x = \_ -> x -- aka const
f >>= g = \r -> g (f r) r
如果仔细研究,我们会注意到 >>=
实际上是 (->) r
类型的 <*>
的翻转版本。但是当我说 flipped 时要小心,它并不意味着 g <*> f
== f >>= g
.
g <*> f = \x -> g x (f x)
f >>= g = \x -> g (f x) x
这有点令人兴奋,所以系好安全带:-)
monad 是一种接受一个类型参数的类型构造函数。例如,Maybe
就是这样一个类型构造函数。所以,Maybe
是一个 monad,然后 Maybe Int
是那个 monad 中的一个 monadic 值。类似地,IO
是一个 monad,然后 IO Int
是那个 monad 中的一个 monadic 值。
现在,也可以使用具有 两个 类型参数的类型构造函数来创建 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"
。这个中缀表示法可以是 "canonicalized" 这样的:
(->) a b
实际上,"function" 可以看作是一个具有两个类型参数的类型构造函数,就像 Either
一样。对于"function"构造函数来说,这些参数都有特定的含义——一个是"function input",另一个是"function output".
好的,现在我们准备好将函数视为 monad。就像 Either
一样,为了做到这一点,我们必须修复第一个类型参数。因此,例如,(->) Bool
是一个单子,然后 (->) Bool Int
(也称为 Bool -> Int
)是该单子中的一个单子值。类似地,(->) String
是一个 完全不同的 monad,然后 (->) String Int
(也称为 String -> Int
)是那个 monad 中的一个 monadic 值。
为了给你一个直觉,一种看待它的方式是这样的 monad 中的 monadic 值意味着 "promise" - 也就是说,“你给我一个 String
然后我给你一个 Int
回 ”。然后标准的 monadic 组合让您可以将这些承诺组合在一起,就像您编写 IO
动作一样。
到现在为止?好的,很好。
现在让我们看看如何实现绑定(又名 >>=
)。 >>=
的签名如下:
(>>=) :: m a -> (a -> m b) -> m b
现在让我们将其专门用于函数。假设现在 m ~ (->) Bool
。然后我们有:
(>>=) :: (->) Bool a -> (a -> (->) Bool b) -> (->) Bool b
或者,如果我们以中缀形式重写 (->)
构造函数,我们得到:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
所以你看 - 绑定采用 "promise of a
",然后是从 a
到 "promise of b
" 的函数,然后是 returns 和 "promise of b
"。那么,实现是微不足道的:
(>>=) :: (Bool -> a) -> (a -> (Bool -> b)) -> (Bool -> b)
(>>=) promiseOfA f = \theBool -> f (promiseOfA theBool) theBool
这里我们创建了一个新的 "promise",它是一个接受 Bool
的函数,这个 "promise" 所做的是将给定的 Bool
传递给 "promise of a
",然后将结果a
传递给函数f
,其中returns一个"promise of b
",我们再将相同的Bool
传递给函数,最终得到结果 b
.
这里注意一下:看看theBool
是如何被使用两次的——先传给promiseOfA
然后再传给f
的结果?这就是 "reducing of the number of arguments" 发生的地方。这是我们两次传递参数的地方。
当然,这不一定只适用于 Bool
s。任何输入类型都是公平的游戏。所以我们可以这样概括它:
(>>=) :: (input -> a) -> (a -> (input -> b)) -> (input -> b)
(>>=) promiseOfA f = \i -> f (promiseOfA i) i
(与the actual definition from the standard library比较)。
呸!
好的,现在我们终于可以查看您的原始示例了。首先,看reverse
。由于它是一个函数,我们可以将其视为 monad (->) [a]
中的 monadic 值 - 即 "promise of [a]
",其中 "input" 也是 [a]
.
然后,(==)
的签名:
(==) :: Eq x => x -> x -> Bool
(请注意,我故意将 a
替换为 x
:不要将其与 reverse
签名中的 a
混淆 - 它们是两个不同的 a
s,不必是同一类型)
我们可以将此签名视为一个接受 x
和 returns 另一个 x -> Bool
类型函数的函数。所以:
(==) :: x -> (x -> Bool)
这可以看作是 bind 的第二个参数,a -> m b
类型的参数。为了这样看,我们需要说 a ~ x
和 m ~ (->) x
和 b ~ Bool
。所以这里讨论的 monad 是 (->) x
- 即 "promise" 输入类型为 x
.
但是等等!为了将此函数绑定到 reverse
,它们需要在同一个 monad 中!这意味着 x ~ [a]
。这反过来意味着 (==)
的类型被固定为:
(==) :: [a] -> ([a] -> Bool)
因此,当我们调用 (>>=)
将其作为第一个参数传递 reverse
并将 (==)
作为第二个参数传递时,我们得到:
reverse >>= (==)
= \i -> (==) (reverse i) i -- by my definition above
= \i -> reverse i == i