(x*) 中的星号在 Haskell 中有什么作用?

What does the asterisk in (x*) do in Haskell?

我是 Haskell 的新手,有人可以向我解释这段代码的工作原理吗?

f = g (\x -> x)
g k [] = k 100
g k (x:xs) = g ((x*) . k) xs

当我调用f [1..5]时,它是returns12000。我不明白为什么。 (x*) 是做什么的?

那是运算符部分。 (x*) 等价于 \y -> (x * y),一个将其自变量乘以 x 的函数。

您可以将运算符部分与 任何 运算符一起使用,而不仅仅是 *。这包括用反引号括起来的普通函数。例如,以下所有内容都是等效的:

  • (3 `elem`)
  • (elem 3)
  • \xs -> 3 `elem` xs
  • \xs -> elem 3 ex

这是乘法。实际上这适用于 (*) :: Num a => a -> a -> a. It uses the section of an infix operator [Haskell-wiki].

这意味着 (x *) 具有类型 Num a => a -> aa 变量类型 x,因此它将 相乘x.

给定的值

您可以扩展定义,观察发生了什么。

f [1..5]
= g (\x -> x) [1..5]
= g ((1*) . (\x -> x)) [2..5]
= g ((2*) . (1*) . (\x -> x)) [3..5]
= g ((3*) . (2*) . (1*) . (\x -> x)) [4..5]
= g ((4*) . (3*) . (2*) . (1*) . (\x -> x)) [5]
= g ((4*) . (3*) . (2*) . (1*) . (\x -> x)) [5]
= g ((5*) . (4*) . (3*) . (2*) . (1*) . (\x -> x)) []
= ((5*) . (4*) . (3*) . (2*) . (1*) . (\x -> x)) 100
= ((5*) . (4*) . (3*) . (2*) . (1*)) 100
= ((5*) . (4*) . (3*) . (2*)) 100
= ((5*) . (4*) . (3*)) 200
= ((5*) . (4*)) 600
= (5*) 2400
= 12000

注意(x*)是函数“乘以x”,即\y -> x*y,而运算符.是函数组合。所以组合链 (5*) . (4*) . ... . (1*) 是函数“将输入乘以 1,然后乘以 2,然后乘以 3,...,然后乘以 5”。

您的代码是以所谓的连续传递样式 (CPS) 编写的,我们不是从初始值 (100) 开始并对其应用函数,而是执行(尾)递归调用和“将一些函数附加到当前的“延续”k(在您的代码中使用 (x*) . k)。这样做会构建一长串组合函数,并且仅在最后我们将该链应用于初始值 (100)。这种代码风格不是特别好读,总的来说。

这不是某种特殊的星号符号,它只是标准的乘法运算符。

Prelude> 5*7
35

与任何中缀运算符一样,您可以使用运算符的部分

Prelude> (5*) 7
35
Prelude> (*7) 5
35

为了更清楚地说明发生了什么,以非交换运算符为例:

Prelude> 15 / 3
5.0
Prelude> (15/) 3
5.0
Prelude> (/3) 15
5.0

一个部分的重点是你得到一个函数对应于获取第二个操作数,同时保持已经提供的操作数不变,比如

Prelude> map (/3) [21, 30, 45]
[7.0,10.0,15.0]
Prelude> map (24/) [3,4]
[8.0,6.0]

如果 x 具有类型,例如 Int,则 (x*) 具有类型 Int -> Int。这样的函数可以与另一个函数组合,该函数使用 . 运算符给出 Int 作为结果,例如

Prelude> ((4*) . length) "bla"
12       -- same as
Prelude> 4 * length "bla"
12

这样的组合函数不仅可以立即应用于参数,还可以映射到列表(或通常作为高阶函数的参数传递):

Prelude> map ((4*) . length) ["bla", "blub"]
[12,16]

在您的示例中,g 是这样一个高阶函数:

g :: (Int -> Int) -> [Int] -> Int

它以一个函数作为第一个参数,在调用中 g (\x -> x) [1..5] 只是恒等函数(又名 g id [1..5])。

但在递归调用中,g“修改”了该函数,post-将其与乘法部分组合在一起。例如,

g id [2] ≡ g ((2*) . id) []
         ≡ ((2*) . id) 100
         ≡ (2*) 100
         ≡ 2 * 100
         ≡ 200

如果列表更长,那就是

g id [7,8] ≡ g ((7*) . id) [8]
           ≡ g ((8*) . ((7*) . id)) []
           ≡ ((8*) . ((7*) . id)) 100
           ≡ 8 * 7 * id 100
           ≡ 5600