【问题标题】:You can find out if a list is a palindrome using (==) <*> reverse. How does it work?您可以使用 (==) <*> reverse 确定列表是否为回文。它是如何工作的?
【发布时间】:2026-01-28 04:55:02
【问题描述】:

我尝试使用这些类型来解决这个问题,但我仍然难以理解它是如何工作的。

给定:

> :t (==)
(==) :: Eq a => a -> a -> Bool

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

> :t reverse
reverse :: [a] -> [a]

> :t (==) <*> reverse
(==) <*> reverse :: Eq a => [a] -> Bool

直观地说,我可以理解它将相等运算符与反向组合在一起,从而创建一个检查反向列表是否等于原始列表的函数,但这实际上并没有比已经非常明显的信息多多少。

有人可以更详细地分解这里实际发生的内部情况吗?

【问题讨论】:

    标签: list haskell reverse palindrome


    【解决方案1】:

    (&lt;*&gt;)的类型开头

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

    现在(-&gt;) kApplicative 的一个实例,带有实现

    instance Applicative ((->) k) where
        pure a  = \_ -> a
        f <*> g = \k -> f k (g k)
    

    特别是,(&lt;*&gt;) 的类型专门用于(-&gt;) k

    (<*>) :: (k -> a -> b) -> (k -> a) -> (k -> b)
    

    所以应用程序(==) &lt;*&gt; reverse

    (==) <*> reverse = \k -> (==) k (reverse k)
                     = \k -> k == reverse k
    

    即它检查一个列表是否等于它的反向。

    【讨论】:

    • (-&gt;) k 是什么意思是 lambda?
    • 一些背景:((-&gt;) t) 是“一个以t 作为输入的函数”,在你的情况下tString。它的 Applicative 实例接收 String,并将其传递给第一个函数 f,然后返回另一个函数 f'。它将同一个字符串传递给第二个函数,取回某种值;然后它用返回值调用f'。因此(f &lt;*&gt; g) x == f x (g x).
    • @amalloy 我认为我的很多困惑来自&lt;*&gt; 的类型签名如何变化。例如f (a -&gt; b) 如何变成(k -&gt; a -&gt; b)。为什么(-&gt;) k 会出现这种情况?
    • 它的类型签名没有改变,但是我们使用了一个更专业的版本。 &lt;*&gt; 适用于任何 Applicative,就像 == 适用于任何 Eq。 (&lt;*&gt;) :: Applicative f =&gt; f (a -&gt; b) -&gt; f a -&gt; f b,在我们的例子中是f :: ((-&gt;) String)。所以我们替换f,到达(&lt;*&gt;) :: (String -&gt; a -&gt; b) -&gt; (String -&gt; a) -&gt; (String -&gt; b)。此外,a :: Stringb :: Bool,所以(&lt;*&gt;) :: (String -&gt; String -&gt; Bool) -&gt; (String -&gt; String) -&gt; (String -&gt; Bool)。最后,去掉无关的(),得到(&lt;*&gt;) :: (String -&gt; String -&gt; Bool) -&gt; (String -&gt; String) -&gt; String -&gt; Bool
    【解决方案2】:

    Chris Taylor 的回答恰到好处,但另一种看待它的方式,我觉得更直观,是这样的:Applicative 函数类型实例的作用是这样的:

    1. 将相同的参数值“提供”给具有相同参数类型的两个函数;
    2. 将它们的结果与另一个函数结合起来。

    所以基本上,如果您有 f :: t -&gt; ag:: t -&gt; bApplicative 实例允许您将函数 h :: a -&gt; b -&gt; c 映射到 ab 结果,假设 f 和 @987654329 @ 将得到相同的参数。

    所以想想你会如何以一种不复杂的方式编写回文测试:

    palindrome :: Eq a => [a] -> Bool
    palindrome xs = xs == reverse xs
    

    xs 在定义的右侧出现两次:一次作为== 的参数,第二次作为reverse 的参数。这会自动告诉您可能有一种方法可以使用 (-&gt;) t 应用程序实例来消除重复。一个不同的,也许更直观的攻击方法是首先将函数重写为:

    palindrome xs = id xs == reverse xs
    

    ...其中id x = x身份函数,它只返回其参数(并且是标准库函数)。现在您可以使用标准 Applicative 成语 (f &lt;$&gt; a0 &lt;*&gt; ... &lt;*&gt; an) 将其重写为:

    -- Function that feed the same argument value to both `id` and `reverse`,
    -- then tests their results with `==`: 
    palindrome = (==) <$> id <*> reverse
    

    现在我们可以询问是否有办法在重写中摆脱id。因为&lt;$&gt;只是fmap的简写,所以我们可以研究(-&gt;) tFunctor实例,这只是表达函数组合的另一种方式:

    instance Functor ((->) t) where
      -- Mapping `f` over a *function* `g` is just the same as composing `f`
      -- on the results of `g`.
      fmap f g = f . g
    

    函数组合最重要的属性之一是对于任何函数f

    f . id = f
    

    因此,将其应用于上述palindrome 的版本,我们得到:

    -- Since `f . id = f` for all `f`, then `(==) <$> id` is just `(==)`:
    palindrome = (==) <*> reverse
    

    【讨论】:

    • 为什么你总是必须想出漂亮、清晰的答案?
    • 两个月后回到它更有意义。谢谢!
    最近更新 更多