下面的代码取自Elm Form Example第 122 行,<<
运算符是什么意思?
Field.field Field.defaultStyle (Signal.send updateChan << toUpdate) "" content
在Elm 语法参考中找不到它。
这是否意味着,当字段更改时,而不是将其发送content
到updateChan
,发送toUpdate
到updateChan
?
下面的代码取自Elm Form Example第 122 行,<<
运算符是什么意思?
Field.field Field.defaultStyle (Signal.send updateChan << toUpdate) "" content
在Elm 语法参考中找不到它。
这是否意味着,当字段更改时,而不是将其发送content
到updateChan
,发送toUpdate
到updateChan
?
<<
是一个函数组合运算符,在核心库中定义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
具有 type List a -> Bool
。它接受List
某种类型的 a 并返回 type 的值Bool
。
如果您再次看到相同类型的变量,则该类型变量的实例必须是相同类型。例如List.reverse
有类型List a -> List a
。因此,如果您应用于List.reverse
整数列表(即具有 type 的东西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
。我们可以将类型解读为“<code>List.member 接受 2 个参数,分别为 typea
和 type List a
”。但我们也可以将类型解读为“<code>List.member 接受 1 个类型的参数a
。它返回一个类型为List a -> Bool
”的函数。因此我们可以创建一个函数isOneMemberOf = List.member 1
,它的类型为List Int -> Bool
.
这意味着->
函数的类型注释是右关联的。换句话说,a -> List a -> Bool
与 相同a -> (List a -> Bool)
。
任何中缀运算符实际上都是幕后的普通函数。只是当函数名称仅由非字母数字符号(如 $、<|、<< 等)组成时,它被放置在 2 个参数之间,而不是在它们前面(如普通函数)。
但是您仍然可以将二元运算符放在+
2 个参数的前面,方法是将其括在括号中,因此下面的 2 个函数应用程序是等效的:
2 + 3 -- returns 5
(+) 2 3 -- returns 5, just like the previous one
中缀运算符只是普通函数。他们没有什么特别的。您可以像任何其他功能一样部分应用它们:
addTwo : Int -> Int
addTwo = (+) 2
addTwo 3 -- returns 5
(<<)
是一个函数组合运算符,在核心库中定义Basics
。所有基础功能都被导入到 Elm 项目中,这意味着您不必编写import Basics exposing (..)
,默认情况下已经完成。
因此,就像任何其他运算符一样,(<<)
它只是一个函数,就像任何其他运算符一样。它的类型是什么?
(<<) : (b -> c) -> (a -> b) -> a -> c
因为->
是右结合的,这相当于:
(<<) : (b -> c) -> (a -> b) -> (a -> c)
换句话说,分别(<<)
接受两个类型的函数b -> c
和a -> b
,并返回一个类型的函数a -> c
。它将2个功能合二为一。这是如何运作的?为简单起见,让我们看一个人为的例子。假设我们有 2 个简单的函数:
addOne = (+) 1
multTwo = (*) 2
假设我们没有(+)
, only addOne
,我们将如何创建一个加 3 而不是 1 的函数?很简单,我们addOne
一起作曲 3 次:
addThree : Int -> Int
addThree = addOne << addOne << addOne
如果我们想创建一个加 2 然后乘以 4 的函数怎么办?
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
应用于 valuex
时,您会知道:
f
于x
g
上一步的结果h
上一步的结果因此(negate << (*) 10 << sqrt) 25
等于-50.0
,因为首先取 25 的平方根得到 5,然后将 5 乘以 10 得到 50,然后将 50 取反得到 -50。
在 Elm 0.13(见公告)之前,函数组合运算符是(.)
,其行为与 current 相同(<<)
。(<<)
在 Elm 0.13 中采用 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等语言仍然拥有它。
<<
is a function composition - returns function.
Composition creates a pipe of computations, chain of functions. This pipe waits for input, and when provided, first function starts computation, sends output to next etc.
import Html
add x y =
Debug.log "x" x + Debug.log "y" y
add9 =
add 4 << add 5
main =
Html.text <| toString <| add9 2
Note: In the above example I use partial application. It means that I don't provide all parameters to function and as a result I get function.
If you run above example in web browser and look at the console output, you will see:
x: 5
y: 2
x: 4
y: 7
If we write it as math operations it will look like this:
4 + (5 + 2)
4 + 7
Note: We can also use forward version >>
.
Looking at signature of this operator:
(<<) : (b -> c) -> (a -> b) -> a -> c
For <<
operator, there is a function b -> c
as the first parameter, and a function a -> b
as the second:
(b -> c) << (a -> b)
But there is also a third parameter a
. Because ->
is right-associative, then
(<<) : (b -> c) -> (a -> b) -> a -> c
is equivalent to:
(<<) : (b -> c) -> (a -> b) -> (a -> c)
.
So that <<
returns function a -> c
.
In programming languages, the associativity (or fixity) of an operator is a property that determines how operators of the same precedence are grouped in the absence of parentheses; i.e. in what order each operator is evaluated:
a = b = c
is parsed as a = (b = c)
Here I use <<
as infix operator, but we can also use it as a prefix operator enclosing it with parenthesis: (<<) (b -> c) (a -> b)
or (<|) (add 4) (add 5)
.
elm < 0.18 used to let you take normal functions and use them as infix operators.
<|
operator<|
is a function application - returns value
We basically use it instead of parentheses.
text (something ++ something)
can be written as
text <| something ++ something
So looking at signature of this operator:
(<|) : (a -> b) -> a -> b
we can see that for <|
operator, there is a function a -> b
as the first parameter, and value a
as the second:
(a -> b) <| a
and it returns b
.
We can get the same value with function application <|
:
v1 = add 4 <| add 5 <| 4
v2 = (add 4 << add 5) 4
|>
.<|
and <<
是功能组合。对于您的具体示例,这意味着
\x -> (Signal.send updateChan (toUpdate x))
在 elm 中,它不是语法的一部分,而是标准库的一部分:Basics.<<
<|
在这个回复中,我展示了和之间的区别<<
。
> 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