【问题标题】:Explanation of <$> and <*> when used with functions<$> 和 <*> 与函数一起使用时的解释
【发布时间】:2023-03-16 03:49:01
【问题描述】:

我目前正在学习 Haskell,我很难理解如何解释 &lt;$&gt;&lt;*&gt; 的行为。

在某些情况下,这一切都来自搜索如何在使用takeWhile 时使用or 操作,而我找到的答案是这个

takeWhile ((||) <$> isDigit <*> (=='.'))

在我看到的大多数文档中,&lt;*&gt; 与容器类型一起使用。

show <*> Maybe 10

通过观察

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

它告诉我 保留外部容器,如果它的内容,并将权利应用于内部,然后将其包装回容器中

   a       b          f       a        f       b
([Int] -> String) -> [Just]([Int]) -> [Just]([String])

这对我来说很有意义,在我看来 f a 本质上是在容器内发生的,但是当我尝试相同的逻辑时,我可以理解,但我无法关联逻辑

f = (+) <$> (read)

所以f 变成了

   a           b              f           a          f            b
([Int] -> [Int -> Int]) -> ([String] -> [Int]) -> ([String] -> [Int -> Int])

所以f 是容器,当我尝试弄清楚这段代码将要做什么时,我真的很困惑。我明白当我这样写出来时,我可以计算出来,它基本上等同于.

(.) :: (b -> c) -> (a -> b) -> a -> c

  b           c               a           b          a            c
([Int] -> [Int -> Int]) -> ([String] -> [Int]) -> ([String] -> [Int -> Int])

所以可以写成

f = (+) . read

为什么不就这么写呢?为什么原来的sn-p不直接写成

takeWhile ((||) . isDigit <*> (=='.'))

或者&lt;$&gt; 在这种情况下是否暗示了. 没有的东西?


现在看&lt;*&gt;,好像和基本一模一样,只是它需要两个容器,使用两个容器的内部,然后把它装进容器中

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

所以

Just show <*> Just 10

   f      a         b           f       a             f     b
[Just]([Int->Int]->[Int]) -> [Just]([Int->Int]) -> [Just]([Int])

但是,对于函数,事物如何相互传递变得模糊不清。 查看原始片段并将其分解

f1 :: Char -> Bool -> Bool
f1 = (||) . isDigit 

f2 :: Char -> Bool
f2 = f1 <*> (== '.')

f2 中的&lt;*&gt; 行为是

   f        a          b          f          a           f       b
([Char] -> [Bool] -> [Bool]) -> ([Char] -> [Bool]) -> ([Char] -> [Bool])

所以使用之前的逻辑,我认为Char -&gt; 是容器,但在了解正在发生的事情时它对我来说不是很有用。

在我看来&lt;*&gt; 将函数参数传递到右侧,然后传递相同的函数参数,并将返回值传递到左侧? 所以对我来说,它看起来相当于

f2 :: Char -> Bool
f2 x = f1 x (x=='_')

当我看到&lt;*&gt;&lt;$&gt; 时,我想弄清楚数据在哪里流动,这对我来说是一种心理体操。我想我只是在寻找一个有经验的haskell-er如何在他们的脑海中阅读这些操作。

【问题讨论】:

  • 之间的区别。和 stackoverflow.com/questions/27883414/… 还可以阅读 typeclassopedia wiki.haskell.org/Typeclassopedia中的函子和应用部分@
  • 在函数上,.&lt;$&gt; 之间没有区别。至于体操,在弄清楚为什么会这样之后,你只需记住(f &lt;*&gt; g) x = f x $ g x。还有(h &lt;$&gt; f &lt;*&gt; g) x = (liftA2 h f g) x = f x `h` g x = (h . f) x (g x)
  • 只是一个评论。对于每个 Applicative 函子,以下表达式 f &lt;$&gt; x &lt;*&gt; y 等价于 liftA2 f x y。事实证明,函数实例的liftA2 非常方便,值得学习。本质上是liftA2 c f g ==\x -&gt; c (f x) (g x)。例如(||) &lt;$&gt; isDigit &lt;*&gt; (=='.') == liftA2 (||) isDigit (== '.') == \x -&gt; isDigit x || x == '.'
  • 函数是一个容器。它包含由参数索引的值。
  • @Ammar “差异 [in that link]”是指“no 差异 [as shown in that link]”。

标签: haskell


【解决方案1】:

函数的应用实例非常简单:

f <*> g = \x -> f x (g x)

您可以自己验证类型是否匹配。正如你所说,

(<$>) = (.)

(忽略固定性)

所以你可以重写你的函数:

(||) <$> isDigit <*> (=='.')
(||) . isDigit   <*> (=='.')
\x -> ((||) . isDigit) x ((=='.') x)

-- Which can simply be rewritten as:
\x -> isDigit x || x == '.'

但重要的是要了解函数实例为何如此以及它是如何工作的。让我们从Maybe开始:

instance Applicative Maybe where
    pure :: a -> Maybe a
    pure x = Just x

    (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b    
    Nothing  <*> _        = Nothing
    _        <*> Nothing  = Nothing
    (Just f) <*> (Just x) = Just (f x)

忽略这里的实现,只看类型。首先,请注意我们已将Maybe 设为Applicative 的实例。 Maybe 到底是什么?你可能会说它是一种类型,但事实并非如此——我不能写类似的东西

x :: Maybe

- 这没有意义。相反,我需要写

x :: Maybe Int
-- Or
x :: Maybe Char

Maybe 之后的任何其他类型。所以我们给Maybe一个像IntChar这样的类型,它突然变成了一个类型本身!这就是为什么Maybe 是所谓的类型构造函数。
这正是Applicative 类型类所期望的——一个类型构造函数,您可以将任何其他类型放入其中。所以,用你的比喻,我们可以考虑给Applicative一个容器类型。

现在,我的意思是什么

a -> b

?

我们可以使用前缀符号重写它(同样的方式1 + 2 = (+) 1 2

(->) a b

在这里我们看到箭头(-&gt;) 本身也只是一个类型构造函数——但与Maybe 不同的是,它需要两种 类型。但是Applicative 只想要一个采用一种类型的类型构造函数。所以我们给它:

instance Applicative ((->) r)

这意味着对于任何r(-&gt;) r 都是Applicative。继续容器类比,(-&gt;) r 现在是任何类型b 的容器,因此结果类型为r -&gt; b。这意味着 contained 类型实际上是函数在给它一个r 时的未来结果

现在是实际实例:

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

(-&gt;) r替换为应用程序,

(<*>) :: ((->) r (a -> b)) -> ((->) r a) ((->) r b)
-- Rewriting it in infix notation:
(<*>) :: (r -> (a -> b)) -> (r -> a) -> (r -> b)

我们将如何编写实例?好吧,我们需要一种从容器中取出包含类型的方法——但我们不能像使用Maybe 那样使用模式匹配。因此,我们使用 lambda:

(f :: r -> (a -> b)) <*> (g :: r -> a) = \(x :: r) -> f x (g x)

f x (g x) 的类型是b,所以整个lambda 的类型是r -&gt; b,这正是我们要找的!

编辑:我注意到我没有谈论函数的pure 的实现 - 我可以更新答案,但尝试看看你是否可以使用类型签名来解决它自己!

【讨论】:

    猜你喜欢
    • 2019-10-28
    • 1970-01-01
    • 1970-01-01
    • 2019-03-06
    • 2021-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    相关资源
    最近更新 更多