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]