谁能解释一下这个 Haskell 谜题?(Haskell 是一种编程语言)

4

我知道使用 . 运算符可以将函数链接在一起,例如:

isLessThanZero x 
   | x < 0 = True
   | otherwise = False

(isLessThanZero . negate) 3 -- Produces True

或者使用美元符号$:

getNegNumbers x = map (*x) [-1, -2, -3]

filter isLessThanZero $ getNegNumbers 2 -- Produces [-2, -4, -6]

但是如果我想要做类似于这样的事情:

(subtract . negate) 1 2 -- 3
negate $ subtract 1 2 -- -1

这里的结果不同,这是没有意义的,因为这两个函数接受的参数数量不同。使用 .negate 函数检查一个数是否为负数,但提供了两个参数。这可能意味着表达式是左关联的。
negate (subtract 1 2) -- -1
(subtract . negate) 1 2 -- 3

但这很令人困惑,因为在第一个示例中:

(isLessThanZero . negate) 3

表达式产生了True,这意味着函数negate首先被执行,然后调用isLessThanZero。但在最新的示例中,似乎是先调用subtract,然后再调用negate。所以我不确定这里到底发生了什么。但更令人困惑的是:
subtract 1 2 -- Produces 1!

这意味着整个表达式:
(subtract . negate) 1 2 -- Produces 3!

函数链使用时的副作用。

我的理论是这样分解的:

我们知道2 -(-1)= 3。 所以,表达式仍然是向右结合的..我认为。 negate仍然首先被调用,就像前面的例子一样,只是它影响第一个参数,而不是两个参数,这是有道理的,因为negate只接受一个参数,我们根本不需要对函数进行映射。

那么,当使用参数数量不同的函数进行链接时,Haskell应该如何响应呢?


1
您可以通过在 GHCi 中使用 :i 命令来查找任何操作符的结合性和优先级::i . 会返回 [...] infixr 9 . - David Young
3个回答

4
在Haskell中,函数是柯里化的,因此:
f :: a -> b -> c

与以下代码等效:

f :: a -> (b -> c)

因此,当您计算f 1 2时,首先将a应用于f,得到一个具有类型b -> c的函数。然后应用b并得到c

现在,让我们遵循类型:

Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

Prelude> :t negate
negate :: Num a => a -> a

Prelude> :t subtract
subtract :: Num a => a -> a -> a

Prelude> :t (subtract.negate)
(subtract.negate) :: Num b => b -> b -> b

那么,(subtract.negate) 2的类型是什么?
Prelude> :t (subtract.negate) 1
(subtract.negate) 1 :: Num b => b -> b

您可以看到,negate 获得了 1 并给出了 -1,但 subtract 拿到了那个 -1 并给出了 Int -> Int。然后,您将 2 应用于那个 Int -> Int,并得到了 3

简而言之,(.) 总是右结合的,接受 (b -> c)(a -> b)。唯一棘手的部分是,c 本身可以是 (d -> e)


4

Haskell的方法是将所有函数视为只接受一个参数并返回一个值,即使对于具有多个参数的函数也是如此。

因此,其签名为subtract函数:

subtract :: Num a => a -> a -> a

也可能看到:
subtract :: Num a => a -> (a -> a)

一个以数字为参数的函数,返回一个以数字值为参数并返回数字值的函数。
考虑到(.),它的签名是:
(.) :: (y -> z) -> (x -> y) -> x -> z

它需要两个函数并返回一个函数。

如果应用于(subtract . negate),则将具有此签名:

(subtract . negate) :: Num a => (a -> (a -> a)) -> (a -> a) -> (a -> (a -> a))

其中:

x = a
y = a
z = a -> a

这个函数的签名是完全有效的。

请注意,subtract 1 2 的作用类似于 2 - 1

(subtract . negate) 函数是一个函数,它接受一个数值,对其取反并返回另一个函数,该函数将从中减去取反的值。

还要注意,negate (subtract 1 2) 等于 -1,而不是 3


2

这可能意味着表达式是左结合的。

我不明白你在这里所说的“less-associative”是什么意思。


函数应用是左结合的,意味着 a b c 解析为 (a b) c

组合定义如下:(a . b) c = a (b c)

因此

(subtract . negate) 1 2
  =                        -- function application associates to the left
((subtract . negate) 1) 2
  =                        -- definition of composition
(subtract (negate 1)) 2
  =                        -- definition of subtract
2 - (negate 1)
  =                        -- definition of negate
2 - (-1)
  =                        -- definition of -
3

另一种表达方式如下(带有a $ b = a b):
negate $ subtract 1 2
  =                       -- function application has higher precedence than any operator
negate $ (subtract 1 2)
  =                       -- definition of $
negate (subtract 1 2)
  =                       -- definition of negate
-(subtract 1 2)
  =                       -- definition of subtract
-1

那么函数组合符号 . 和函数应用符号 $ 都是从右到左结合的,对吗? - Poriferous
@Poriferous 对于 . 来说并不重要,因为 a . (b . c) = (a . b) . c$ 是右结合的,因为 a $ b $ c 解析为 a $ (b $ c) - melpomene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接