<<
是一个函数组合运算符,在Basics
核心库中定义。所有来自Basics的函数都会被无条件地导入到Elm项目中。
让我们回顾一下Elm类型系统的基础知识。
Elm是静态类型的。这意味着在Elm中,每个变量或函数都有一个类型,并且这种类型永远不会改变。在Elm中的类型示例包括:
Int
String
Maybe Bool
{ name : String, age : Int }
Int -> Int
Int -> String -> Maybe Char
。String -> String
的函数接收或返回Int
,这样的代码甚至不会编译。String
或Maybe Int
)来使您的函数具有多态性,类型变量是任意的小写字符串,例如a
。许多Elm核心函数都是类型多态的,例如List.isEmpty
的类型为List a -> Bool
。它接受某种类型的List
并返回一个Bool
值。List.reverse
的类型为List a -> List a
。因此,如果你将List.reverse
应用于整数列表(即具有类型List Int
的内容),它将返回一个整数列表。这种函数不可能接收整数列表,然后返回字符串列表。编译器保证了这一点。
Elm中的所有函数默认都是柯里化的。这意味着,如果你有一个带有2个参数的函数,它会被转换成一个带有1个参数的函数,该函数返回一个带有1个参数的函数。这就是为什么Elm中的函数应用语法与Java、C++、C#、Python等其他语言中的函数应用不同的原因。当你可以写someFunction arg1 arg2
时,没有理由去写someFunction(arg1, arg2)
。为什么?因为实际上someFunction arg1 arg2
等同于((someFunction arg1) arg2)
。
List.member
。 List.member
的类型是a -> List a -> Bool
。我们可以将该类型解读为“List.member
需要两个参数,一个是类型为a
,另一个是类型为List a
”。但我们也可以将该类型解读为“List.member
需要一个类型为a
的参数。它返回一个类型为List a -> Bool
的函数”。因此,我们可以创建一个函数isOneMemberOf = List.member 1
,它的类型为List Int -> Bool
。->
是右结合的。换句话说,a -> List a -> Bool
与a -> (List a -> Bool)
是相同的。
任何中缀运算符实际上都是一个普通函数。只不过当函数名仅由非字母数字符号(如$、<|、<<等)组成时,它被放置在两个参数之间,而不是在它们前面(像普通函数一样)。
但是您仍然可以将类似+
的二元运算符放在两个参数前面,通过将其括在括号中,因此下面的两个函数应用程序是等价的:
2 + 3 -- returns 5
(+) 2 3 -- returns 5, just like the previous one
中缀运算符只是普通的函数。它们并没有什么特别之处。您可以像任何其他函数一样部分应用它们:
addTwo : Int -> Int
addTwo = (+) 2
addTwo 3 -- returns 5
(<<)
是一个函数组合运算符,在核心库 Basics
中定义。从 Basics 导入的所有函数都是未经限定的,这意味着您不必编写 import Basics exposing (..)
,默认已经完成。
所以像其他运算符一样,(<<)
只是一个函数,就像任何其他函数一样。它的类型是什么?
(<<) : (b -> c) -> (a -> b) -> a -> c
->
是右结合的,因此这等同于:(<<) : (b -> c) -> (a -> b) -> (a -> c)
(<<)
接收两个函数,类型分别为 b -> c
和 a -> b
,并返回一个类型为 a -> c
的函数。它将这两个函数合成为一个函数。那么它是如何工作的呢?为了简单起见,让我们看一个人为制造的例子。假设我们有两个简单函数:addOne = (+) 1
multTwo = (*) 2
(+)
,只有 addOne
,那么如何创建一个加3而不是加1的函数?非常简单,我们将 addOne
组合在一起,共组合3次:addThree : Int -> Int
addThree = addOne << addOne << addOne
ourFunction : Int -> Int
ourFunction = multTwo << multTwo << addOne << addOne
(<<)
从右到左组合函数。但上面的例子很简单,因为所有类型都相同。我们如何找到列表中所有偶数立方体的总和?
isEven : Int -> Bool
isEven n = n % 2 == 0
cube : Int -> Int
cube n = n * n * n
ourFunction2 : List Int -> Int
ourFunction2 = List.sum << filter isEven << map cube
(>>)
是相同的函数,但参数被翻转了,因此我们可以从左到右编写相同的组合:
ourFunction2 = map cube >> filter isEven >> List.sum
当你看到像h << g << f
这样的东西时,你就知道f
、g
和h
是函数。当将这个构造h << g << f
应用于一个值x
时,你就知道:
f
应用于x
g
h
因此,(negate << (*) 10 << sqrt) 25
等于-50.0
,因为你首先对25进行平方根运算得到5,然后将5乘以10得到50,再将50取反得到-50。
(.)
,其行为与当前的 (<<)
相同。 (<<)
是从 F# 语言中采用的(参见Github issue)。 Elm 0.13 还添加了 (>>)
,作为 flip (<<)
的等效形式,并将 (<|)
替换为函数应用运算符 ($)
,并将 (|>)
作为 flip (<|)
的等效形式。
你可能想知道如何将普通的字母数字函数名转换为中缀二进制运算符。在 Elm 0.18 之前,您可以使用反引号使函数成为中缀运算符,因此下面的2个表达式是等价的:
max 1 2 -- returns 2
1 `max` 2 -- returns 2
Elm 0.18 已删除此功能。你不能再在 Elm 中使用它,但像 Haskell 和 PureScript 这样的语言仍然具有该功能。
<<
是函数合成 - 返回一个函数。
函数合成可以创建一条计算管道,一系列的函数链。这个管道等待输入,当有输入时,第一个函数开始计算,将输出发送到下一个函数依次进行处理。
import Html
add x y =
Debug.log "x" x + Debug.log "y" y
add9 =
add 4 << add 5
main =
Html.text <| toString <| add9 2
注意: 在上面的例子中,我使用了部分应用。这意味着我没有向函数提供所有参数,因此我得到了一个函数。
如果您在Web浏览器中运行上述示例并查看控制台输出,您将看到:
x: 5
y: 2
x: 4
y: 7
如果我们用数学运算符来表示它,那么它会看起来像这样:
4 + (5 + 2)
4 + 7
注意:我们也可以使用前向版本>>
。
查看该运算符的签名:
(<<) : (b -> c) -> (a -> b) -> a -> c
对于 <<
运算符,第一个参数是函数 b -> c
,第二个参数是函数 a -> b
:
(b -> c) << (a -> b)
但是也有第三个参数 a
。因为 ->
是右关联的,所以
(<<) : (b -> c) -> (a -> b) -> a -> c
等价于:
(<<) : (b -> c) -> (a -> b) -> (a -> c)
。
因此,<<
返回函数 a -> c
。
在编程语言中,操作符的结合性(或固定性)是一种属性,它确定了没有括号时相同优先级的操作符如何分组;即每个操作符的计算顺序:
a = b = c
被解析为 a = (b = c)
这里我使用 <<
作为 中缀运算符,但我们也可以将其用作前缀运算符,并用括号括起来:(<<) (b -> c) (a -> b)
或者 (<|) (add 4) (add 5)
。
在 Elm 0.18 版本之前,可以将普通函数用作中缀运算符。
<|
运算符的说明<|
是一个函数应用 - 返回值
我们基本上使用它代替括号。
text (something ++ something)
可以写成
text <| something ++ something
因此,查看运算符的签名:
(<|) : (a -> b) -> a -> b
我们可以看到在 <|
操作符中,第一个参数是函数 a -> b
,第二个参数是值 a
:
(a -> b) <| a
它的返回值是 b
。
我们也可以使用函数应用的方式来得到相同的结果:<|
。
v1 = add 4 <| add 5 <| 4
v2 = (add 4 << add 5) 4
这是函数合成。对于你的具体示例,它意味着
\x -> (Signal.send updateChan (toUpdate x))
在 Elm 中,它不是语法的一部分,而是标准库的一部分:Basics.<<
<<
需要两个函数,如果f
和g
都是函数,那么我可以看到f << g
的意思是\x -> f (g x)
。在我的例子中,toUpdate
与谁组合,一些隐藏的恒等函数吗?由于updateChan
不是一个函数,如果你写updateChan << toUpdate
,这会导致错误吗? - Not an IDsend
和 Field 签名,我假设在那里使用了某种数据类型,因此 updateChan
是一个 Channel Data
,而 toUpdate
是一个形式为 String -> Data
的函数。这给出了 (Signal.send updateChan)
具有类型 Data -> Message
,而 (Signal.send updateChan) << toUpdate
具有类型 String -> Message
。 - CheatEx<|
和<<
之间的区别。> addOne a = a + 1
<function> : number -> number
带有<|
的代码示例:
> addThree a = addOne <| addOne <| addOne a
<function> : number -> number
> addThree 4
7 : number
addThree
基于函数组合 <<
的备用版本:
> addThreeComposition = addOne << addOne << addOne
<function> : number -> number
> addThreeComposition 4
7 : number
<<
视为 Haskell 中的.
。 - ZhekaKozlov