【问题标题】:Applicative functors: why can fmap take a function with more than one argument?应用函子:为什么 fmap 可以采用具有多个参数的函数?
【发布时间】:2014-01-27 08:31:14
【问题描述】:

我正在进入 Haskell,发现“learn you a Haskell”这本书最有帮助。我到了applicative functors 的部分。

我对书中出现的以下内容感到困惑:

(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5

产生输出:

[8.0,10.0,2.5]

首先,我已经证实了我在 ghci 中对运算符优先级的怀疑,因此上面的内容等于以下丑陋的陈述:

(((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5 

因此很明显,发生的第一件事是通过(&lt;$&gt;) 中缀运算符调用fmap

这是目前令我难以置信的核心。 fmap(这里显示为中缀(&lt;$&gt;))的定义是:

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

但在我正在努力解决的等式中,(\x y z -&gt; [x, y, z]) 需要三个参数,而不仅仅是一个。那么如何满足(a -&gt; b) 类型的第一个参数呢?

我认为这可能与部分应用程序/柯里化有关,但我无法弄清楚。我将不胜感激。希望我已经很好地提出了这个问题。

【问题讨论】:

  • lambda 演算可能很棘手。

标签: haskell functor applicative


【解决方案1】:

简单的回答:Haskell 中没有多参数的函数!

您可能称之为“二元函数”的候选函数有两个:一个接受(单个!)元组的函数,以及 - 迄今为止在 Haskell 中很流行 - curried 函数。那些只接受一个参数,但结果又是一个函数。

所以,要弄清楚例如fmap (+) 可以,我们写吧

type IntF = Int -> Int

-- (+) :: Int -> IntF
-- fmap :: ( a -> b  ) ->  f a -> f b
--  e.g.:: (Int->IntF) -> f Int->f IntF

在 GHCi 中自行测试:

Prelude> type IntF = Int -> Int
Prelude> let (#) = (+) :: Int -> IntF
前奏> :t fmap (#)
fmap (#) :: Functor f => f Int -> f IntF

【讨论】:

    【解决方案2】:

    考虑类型的函数

    f :: a -> b -> c -> d
    

    其中d 是任何其他类型。由于柯里化,这可以被认为是具有以下类型的函数

    f :: a -> (b -> c -> d)
    

    即一个接受a 并返回b -&gt; c -&gt; d 类型的函数的函数。如果你申请fmap,你有

    -- the type of fmap, which is also :: (a -> r) -> (f a -> f r)
    fmap :: Functor f => (a -> r) -> f a -> f r
    
    -- the type of f
    f :: a -> (b -> c -> d)
    
    -- so, setting r = b -> c -> d
    fmap f :: f a -> f (b -> c -> d)
    

    现在哪个类型适合用作(&lt;*&gt;) 的左侧参数。

    【讨论】:

      【解决方案3】:

      因为你可以使用一个 3 参数的函数,只给它一个参数,这会产生一个 2 参数的函数。所以你最终会得到一个包含 2 个参数的函数列表。然后,您可以再应用一个参数,最终得到一个 1 参数函数列表,最后应用最后一个参数,最终得到一个普通数字列表。

      顺便说一句,这就是 为什么 Haskell 有柯里化函数。它使编写这样的结构变得容易,它适用于任意数量的函数参数。 :-)

      【讨论】:

      • +100 表示“这就是 Haskell 有柯里化函数的原因”——完全正确!它也使得在编译器中操作具有多个参数的代码调用函数变得更加容易。
      【解决方案4】:

      我个人觉得函数的应用函子实例有点奇怪。我将引导您完成这个示例,以尝试直观地理解发生了什么:

      >>> :t (\x y z -> [x, y, z]) <$> (+3)
      ... :: Num a => a -> a -> a -> [a]
      >>> ((\x y z -> [x, y, z]) <$> (+3)) 1 2 3
      [4,2,3]
      

      这会将(+3) 应用于内部函数的第一个参数。其他 2 个外部参数未修改地传递给内部函数。

      让我们添加一个应用程序:

      >>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2)
      ... :: Num a => a -> a -> [a]
      >>> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2
      [4,2,2]
      

      这将(+3) 应用于第一个参数,就像以前一样。使用 applicative,第一个外部参数 (1) 被应用 (*2) 并作为内部函数的第二个参数传递。第二个外部参数不加修改地作为第三个参数传递给内部函数。

      猜猜当我们使用另一个应用程序时会发生什么:

      >>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2)
      ... :: Fractional a => a -> [a]
      >>> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1
      [4.0,2.0,0.5]
      

      对同一个参数的 3 个应用程序作为 3 个参数传递给内部函数。

      这不是理论上可靠的解释,但它可以直观地了解函数的应用实例是如何工作的。

      【讨论】:

        【解决方案5】:

        背景

        让我们从定义&lt;*&gt;pure 作为Applicative 实例的函数开始。对于pure,它将采用任何垃圾值,并返回x。对于&lt;*&gt;,您可以将其视为将x 应用于f,从中获取一个新函数,然后将其应用于g x 的输出。

        instance Applicative ((->) r) where  
            pure x = (\_ -> x)  
            f <*> g = \x -> f x (g x) 
        

        现在,让我们看一下&lt;$&gt; 的定义。它只是fmap 的中缀版本。

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

        回想一下fmap 具有以下实现:

        instance Functor ((->) r) where  
            fmap f g = (\x -> f (g x))  
        

        证明f &lt;$&gt; x 就是pure f &lt;*&gt; x

        让我们从pure f &lt;*&gt; x 开始。将pure f 替换为(\_ -&gt; f)

        pure f <*> x 
        = (\_ -> f) <*> x
        

        现在,让我们应用&lt;*&gt; 的定义,即f &lt;*&gt; g = \q -&gt; f q (g q)

        (\_ -> f) <*> x
        = \q -> (\_ -> f) q (x q)
        

        请注意,我们可以将 (\_ -&gt; f) q 简化为 f。该函数接受我们给它的任何值,并返回f

        \q -> (\_ -> f) q (x q)
        = \q -> f (x q)
        

        这看起来就像我们对fmap 的定义!而&lt;$&gt; 运算符只是中缀fmap

        \q -> f (x q)
        = fmap f x
        = f <$> x
        

        让我们记住这一点:f &lt;$&gt; g 只是 pure f &lt;*&gt; g

        了解(\x y z -&gt; [x, y, z]) &lt;$&gt; (+3) &lt;*&gt; (*2) &lt;*&gt; (/2) $ 5

        第一步是重写表达式的左侧以使用&lt;*&gt; 而不是&lt;$&gt;。使用我们在上一节中刚刚证明的内容:

        (\x y z -> [x, y, z]) <$> (+3)
        = pure (\x y z -> [x, y, z]) <*> (+3)
        

        所以完整的表达式变成了

        pure (\x y z -> [x, y, z]) <*> (+3) <*> (*2) <*> (/2) $ 5
        

        让我们使用&lt;*&gt;的定义来简化第一个运算符

        pure (\x y z -> [x, y, z]) <*> (+3)
        = \a -> f a (g a) --substitute f and g
        = \a -> pure (\x y z -> [x, y, z]) a ((+3) a)
        

        现在让我们将pure x 替换为(\_ -&gt; x)。观察a变成了垃圾值,被用作_,被消耗返回函数(\x y z -&gt; [x, y, z])

        \a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
        = \a -> (\x y z -> [x, y, z]) ((+3) a)
        

        现在让我们回顾一下完整的表达式,然后处理下一个&lt;*&gt;。同样,让我们​​应用&lt;*&gt; 的定义。

        (\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
        = \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)
        

        最后,让我们最后一次重复这个最后的&lt;*&gt;

        (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) <*> (/2)
        = \c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)
        

        请注意,它是一个接受单个值的函数。我们会喂它5

        (\c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)) 5
        (\5 -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 ((/2) 5))
               (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 (2.5   )
               (\5 -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 ((*2) 5))   (2.5   )
                      (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 (10    )    (2.5   )
                      (\5 -> (\x y z -> [x, y, z]) ((+3) 5))   (10    )    (2.5   )           
                             (\x y z -> [x, y, z]) (8     )    (10    )    (2.5   )         
        
        (\x y z -> [x, y, z]) (8) (10) (2.5)                     
        = [8, 10, 2.5]
        

        这就是我们得到最终答案的方式。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-04-25
          • 1970-01-01
          • 1970-01-01
          • 2011-10-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-12
          相关资源
          最近更新 更多