Haskell 应用函子:为什么fmap可以接受具有多个参数的函数?

Haskell 应用函子:为什么fmap可以接受具有多个参数的函数?,haskell,functor,applicative,Haskell,Functor,Applicative,我进入哈斯凯尔,发现《学会哈斯凯尔》一书最有帮助。我要上一节课 我对书中出现的以下内容感到困惑: (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5 首先,我已经证实了我对ghci中运算符优先级的怀疑,因此上述内容等同于以下丑陋的陈述: (((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5 但是在我

我进入哈斯凯尔,发现《学会哈斯凯尔》一书最有帮助。我要上一节课

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

(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
首先,我已经证实了我对ghci中运算符优先级的怀疑,因此上述内容等同于以下丑陋的陈述:

(((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5 
但是在我正在努力解决的方程中,
(\x y z->[x,y,z])
需要三个参数,而不仅仅是一个。那么,如何满足
(a->b)
类型的第一个参数呢

我认为这可能与部分申请/咖喱有关,但我无法理解。我将非常感谢您的解释。希望我把这个问题表达得足够好。

简单的回答: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中自己测试:

前奏曲>输入IntF=Int->Int
前奏曲>让(#)=(+)::Int->IntF
序曲>:t fmap(#)
fmap(#):函子f=>fint->fintf


考虑一个类型的函数

f :: a -> b -> c -> d
其中
d
是任何其他类型。由于咖喱,这可以被认为是具有以下类型的函数

f :: a -> (b -> c -> d)
i、 e.接受
a
并返回
b->c->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)

它现在是右类型,用作
()

的左参数,因为您可以接受一个3参数函数,只向它提供一个参数,这将生成一个2参数函数。因此,您将得到一个2参数函数的列表。然后,您可以再应用一个参数,最后是一个单参数函数列表,最后是最后一个参数,最后是一个普通数字列表


顺便说一句,这就是Haskell使用curry函数的原因。这样可以很容易地编写这样的结构,它适用于任意数量的函数参数。:-)

我个人觉得函数的applicative functor实例有点奇怪。我将带您浏览这个示例,试图直观地了解发生了什么:

>>> :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)
。对于applicative,第一个外部参数(
1
)被应用于
(*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]
>>> :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]
>>:t(\x y z->[x,y,z])(+3)(*2)(/2)
... :: 分数a=>a->[a]
>>>(\x y z->[x,y,z])(+3)(*2)(/2)1美元
[4.0,2.0,0.5]
同一参数的3个应用程序作为3个参数传递给内部函数

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

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

instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x) 
回想一下,
fmap
具有以下实现:

instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))  
证明
fx
只是
纯fx
让我们从纯f x开始。将
纯f
替换为
(\ \uf->f)

这看起来就像我们对
fmap
的定义!而
操作符只是中缀
fmap

(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x  
\q -> f (x q)
= fmap f x
= f <$> x
现在让我们回顾一下完整的表达式,并讨论下一个
。同样,让我们应用
的定义


这就是我们得到最终答案的原因。

lambda演算可能很棘手。+100表示“这就是Haskell使用curried函数的原因”——完全正确!这也使得在编译器中使用多个参数操作代码调用函数变得更容易。
instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))  
pure f <*> x 
= (\_ -> f) <*> x
(\_ -> f) <*> x
= \q -> (\_ -> f) q (x q)
\q -> (\_ -> f) q (x q)
= \q -> f (x q)
\q -> f (x q)
= fmap f x
= f <$> x
(\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
\a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
= \a -> (\x y z -> [x, y, z]) ((+3) a)
(\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
= \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)
(\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)
(\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]