为什么不需要在此函数中提供参数?

Why isn't it necessary to provide a parameter in this function?

我是 Haskell 的新手,本周我在几个讲座幻灯片中发现了这个特殊功能。我试图理解为什么以下函数不需要包含参数:

-- Return all final segments of the argument, longest first 
-- (horrible runtime complexity, used here for illustration purposes only)
tails :: [a] -> [[a]]
tails = reverse . map reverse . inits . reverse

如果我像 tails "thisisastring" 那样称呼它,那么这将是一个有效的论点。是不是必须提供一个参数,比如tails xs = ...。我以前看到的所有其他功能都是这种方式。

该参数是隐式的。或者换句话说,reverse . map reverse . inits . reverse 求值为 [a] -> [[a]].

类型的函数

考虑一个更简单的例子:

double_impl x = x * 2
double = double_impl

这里的double的类型和double_impl的类型是一样的,即它接受一个typeclass Num:

的参数
main = do 
  print $ double_impl 5
  print $ double 5

-- Out: 10
-- Out: 10

我们可以看到 tails 是一个函数,通过检查它的类型。

为了计算它的类型,我们首先记下组合中所有中间函数的类型。请注意,我们为函数的每次出现使用新类型变量。

reverse :: [a] -> [a]
inits :: [b] -> [[b]]
map :: (c -> d) -> [c] -> [d]

现在我们 map reverse 的类型为 [[e]] -> [[e]] 因为我们通过比较表达式

得到了某种类型 ec=d=[e]
reverse :: c -> d  -- for some c and d
reverse :: [e] -> [e] -- for some e

因此最后两个中间体有类型

map reverse :: [[e]] -> [[e]]
reverse :: [f] -> [f]

现在我们开始尝试匹配类型。首先让我强调一下,显然这些不是真实的类型! (抱歉全部大写,但我不想让任何人错过。)

inits . reverse :: [a] -*- [a] = [b] -*> [[b]]
-- I'm using a -*- b -*> c to denote the type a -> c obtained by
-- composing a function of type a -> b with one of type b -> c.
-- The *s are to break the double dashes up,
-- so they aren't parsed as a comment.
-- Anyway, looking at this type, we see
-- we must have [a] = [b], so a = b
-- we can rewrite the type of inits . reverse as
inits . reverse :: [a] -> [[a]]

那么接下来的作文:

map reverse . inits . reverse :: [a] -*- [[a]] = [[e]] -*> [[e]]
-- again, we have [[a]] = [[e]], so e = a, and we have
map reverse . inits . reverse :: [a] -> [[a]]

最后,我们有

reverse . map reverse . inits . reverse :: [a] -*- [[a]] = [f] -*> [f]
-- This time we have [[a]] = [f], so we must have f = [a], so the type
-- of the final composition is
tails = reverse . map reverse . inits . reverse :: [a] -> [[a]]

由于 tails 的类型为 [a] -> [[a]],它必须是一个接受 a 列表作为参数的函数,并且 returns 接受列表列表的函数as.

这称为无点样式(其中 "point" 是一个数学术语,在这里基本上意味着 "argument")。

即使 tails xs = ... 只是 tails = \xs -> ... 的语法糖,所以你需要做的就是让自己相信 tails 是一个函数,就是认识到

  1. reversemap reverseinits都是函数:

    • map是高阶函数;它接受一个函数作为参数,returns 另一个函数。

    • map reverse 是一个函数,因为 map 应用于函数 reverse

  2. 两个函数的组合是另一个函数(假设类型匹配,这样我们就可以专注于每个组合的结果,而不是验证每个组合类型检查。)

因此

  • reverse . map reverse是一个函数,
  • 所以reverse . map reverse . inits是一个函数,
  • reverse . map reverse . inits . reverse是一个函数。

由于tails被赋予了reverse . map reverse . inits . reverse的值,tails本身也是一个函数