Haskell语法:函数是如何组合在一起的?



函数式编程课程练习中的 FileIO.hs 中的一行

getFile :: FilePath -> IO (FilePath, Chars)
getFile = lift2 (<$>) (,) readFile

根据其类型签名,getFile返回IO ( FilePath, Chars),这意味着文件名及其内容的元组。

但我就是想不通为什么会变成这样。

为什么FilePath左边没有变化,而readFile文件名填在右边?

(,)也是应用实例吗?(,)不是IO,那么lift2举起了什么?

而且,有没有办法派生这些类型签名并得到证明?

我知道的语法是,一个函数后面跟着它的参数,它吃掉右手的一个参数,成为一个新函数。但是当涉及到这样的代码时,它对我来说就像一个魔方......

谢谢你帮助我!

附言:额外信息如下

instance Functor IO where
(<$>) =
P.fmap
lift2 ::
Applicative f =>
(a -> b -> c)
-> f a
-> f b
-> f c
lift2 f a b =
f <$> a <*> b
getFiles :: List FilePath -> IO (List (FilePath, Chars))
getFiles = sequence . (<$>) getFile

让我们看看

lift2 (<$>) (,) readFile

这确实是一个简单的函数应用程序:

((lift2 (<$>)) (,)) readFile

(或应用于三个参数lift2)。

所涉及的类型(使用唯一重命名的类型变量以减少混淆)是:

lift2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
(<$>) :: (Functor g) => (j -> k) -> g j -> g k
(,) :: m -> n -> (m, n)
readFile :: FilePath -> IO Chars

我们表达式中的第一件事是将lift2应用于(<$>)。这意味着我们需要以某种方式统一a -> b -> c(lift2的第一个参数的类型)和(Functor g) => (j -> k) -> g j -> g k(<$>的类型)。

那是:

a -> b -> c = (j -> k) -> g j -> g k
-- where g is a Functor
a = j -> k
b = g j
c = g k

这就行了。结果类型为

f a -> f b -> f c
-- where f is an Applicative

这是

f (j -> k) -> f (g j) -> f (g k)

现在这个表达式(lift2 (<$>))应用于(,)。同样,我们必须使类型对齐:

f (j -> k) = m -> n -> (m, n)

在这里,我们利用->是右关联的属性(即a -> b -> c表示a -> (b -> c)),我们可以在类型中使用(柯里)前缀表示法(即a -> b(->) a b相同,与((->) a) b相同)。

f (j -> k) = ((->) m) (n -> (m, n))
f = (->) m
j = n
k = (m, n)

这也行得通。结果类型为

f (g j) -> f (g k)

其中(替换后)成为

((->) m) (g n) -> ((->) m) (g (m, n))
(m -> g n) -> (m -> g (m, n))

这个表达式(lift2 (<$>) (,))适用于readFile。再次,使类型对齐:

m -> g n = FilePath -> IO Chars
m = FilePath
g = IO
n = Chars

并替换为结果类型:

m -> g (m, n)
FilePath -> IO (FilePath, Chars)

这是整个lift2 (<$>) (,) readFile表达式的类型。正如预期的那样,它与getFile :: FilePath -> IO (FilePath, Chars)的声明相匹配。


但是,我们仍然需要验证我们的类约束(Functor gApplicative f)是否已解决。

gIO,这确实是一个Functor(以及ApplicativeMonad)。这里没有什么大的惊喜。

f更有趣:f = (->) m,所以我们需要为(->) m寻找一个Applicative实例。这样的实例确实存在,它的定义包含了getFile实际做什么的答案。

我们可以通过查看lift2的类型(如getFile中使用的)来推导出实例必须是什么样子:

lift2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
lift2 :: (a -> b -> c) -> ((->) m) a -> ((->) m) b -> ((->) m) c
lift2 :: (a -> b -> c) -> (m -> a) -> (m -> b) -> (m -> c)
lift2 :: (a -> b -> c) -> (m -> a) -> (m -> b) -> m -> c

lift2需要

  • ab组合成c的函数,
  • m转换为a的函数,
  • m转换为b的函数,
  • m

并产生c.

它能做到这一点的唯一方法是将m传递到第二个和第三个函数中,并使用第一个函数组合它们的结果:

lift2 f g h x = f (g x) (h x)

如果我们在getFile中内联这个定义,我们得到

getFile = lift2 (<$>) (,) readFile
getFile = x -> (<$>) ((,) x) (readFile x)
getFile = x -> (,) x <$> readFile x
读者心目

lift2实际上是根据<$><*>来定义的。(->) mApplicative实例中的<$><*>有哪些类型?他们的定义必须是什么样子的?

最新更新