haskell 中的 Websocket 示例使用特殊字符作为函数

Websocket example in haskell uses special characters as functions

> type Client = (Text, WS.Connection)
The state kept on the server is simply a list of connected clients. We've added
an alias and some utility functions, so it will be easier to extend this state
later on.
> type ServerState = [Client]
Check if a user already exists (based on username):
> clientExists :: Client -> ServerState -> Bool
> clientExists client = any ((== fst client) . fst)
Remove a client:
> removeClient :: Client -> ServerState -> ServerState
> removeClient client = filter ((/= fst client) . fst)

这是取自 websockets 的文字 haskell 代码。我不明白 clientExists 函数是如何工作的,

clientExists client = any ((== fst client) . fst)

这个函数被调用为,

clientExists client clients

那么,函数如何引用第二个参数clients. 运算符是做什么的?

同样在 removeClient,运算符“/=”代表什么?

如有疑问,请使用 GHCi 解释器找出函数的类型。

首先,/= 运算符是不等于:

ghci> :t (/=)
(/=) :: Eq a => a -> a -> Bool
ghci> 5 /= 5
False
ghci> 10 /= 5
True

.是两个函数的组合。它将两个函数粘合在一起,就像在数学中一样:

ghci> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
ghci> :t head.tail
head.tail :: [c] -> c
ghci> (head.tail) [1, 2, 3]
2

了解了基础知识后,让我们看看如何在您的函数定义中使用它:

ghci> :t (\x -> (== fst x))
(\x-> (== fst x)) :: Eq a => (a, b) -> a -> Bool
ghci> :t (\x-> (== fst x) . fst)
(\x-> (== fst x) . fst) :: Eq b => (b, b1) -> (b, b2) -> Bool
ghci> (\x -> (== fst x) . fst) (1, "a") (1, "b")
True
ghci> (\x -> (== fst x) . fst) (1, "a") (2, "b")
False

正如我们所见,(== fst x) . fst 用于获取两个元组,并比较第一个元素是否相等。现在,这个表达式(我们称它为 fstComp)的类型为 fstComp :: Eq b => (b, b1) -> (b, b2) -> Bool,但我们已经向它传递了一个已定义的元组(client :: (Text, WS.Connection)),我们将其柯里化为 (Text, b2) -> Bool

由于我们有any :: (a -> Bool) -> [a] -> Bool,我们可以将第一个参数与前面的类型统一起来,得到一个(Text, b2) -> [(Text, b2)] -> Bool类型的表达式。实例化 b2 = WS.Connection 我们得到 clientExists :: (Text, WS.Connection) -> [(Text, WS.Connection)] -> Bool 的类型,或者使用类型同义词 clientExists :: Client -> ServerState -> Bool.

.运算符是函数组合运算符,定义为

f . g = \x -> f (g x)

/=运算符是"not equals",在其他语言中通常写成!=

clientExists 函数有两个参数,但第二个参数是多余的,所以省略了。可以写成

clientExists client clients = all ((== fst client) . fst) clients

但是 Haskell 允许您在这种情况下删除最后一个参数。 any 函数的类型为 (a -> Bool) -> [a] -> Bool,函数 any ((== fst client) . fst) 的类型为 [a] -> Bool。这就是说函数 clientExists clientany ((== fst client) . fst).

是同一个函数

另一种思考方式是Haskell没有多参数函数,只有return新函数的单参数函数。这是因为 -> 是右结合的,所以像

这样的类型签名
a -> b -> c -> d

可以写成

a -> (b -> (c -> d))

不改变其含义。对于第二个类型签名,更清楚的是你有一个函数,当给定一个 a、return 时,它是一个类型为 b -> (c -> d) 的函数。如果接下来给它一个 b,它 return 是一个 c -> d 类型的函数。最后,如果给它一个 c 它只是 return 一个 d。由于 Haskell 中的函数应用非常便宜(只是一个 space),这是透明的,但它派上用场了。例如,这意味着你可以写这样的代码

incrementAll = map (+1)

onlyPassingStudents = filter ((>= 70) . grade)

在这两种情况下,我还使用了 运算符部分,您可以在其中向运算符提供任一参数,只要它包含在括号中就可以工作。内部看起来更像

(x +) = \y -> x + y
(+ x) = \y -> y + x

在这里您可以将 + 替换为您喜欢的任何运算符。如果您要扩展 clientExists 的定义以指定所有参数,它看起来更像

clientExists client clients = any (\c -> fst c == fst client) clients

此定义与您的定义完全相同,只是对编译器内部真正使用的内容进行了去糖处理。