What does the `<<` operator mean in elm?

前端 未结 5 1470
悲&欢浪女
悲&欢浪女 2021-01-30 12:24

In the following code taken from Elm Form Example, line 122, what does the << operator mean?

Field.field Field.defaultStyle (Signal.send updat         


        
相关标签:
5条回答
  • 2021-01-30 13:08

    My second attempt :D

    << vs <|

    The difference between << and <| is that << is used to compose functions and <| is used to omit parentheses.

    Why it works like that

    Let's look at type annotation found here:

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

    This definition tells us, that when you pass two functions to function <<, you will get function a -> c.

    Example with demo

    hi a =
        a + 2
    hello a =
        a * 2
    bye =
        hello << hi
    c =
        bye 3
    

    c returns value 10.

    Read more about:

    • infix operators - first argument on the left of function,
    • partial application - when you pass one argument to function expecting two arguments, you get function expecting one argument.
    0 讨论(0)
  • 2021-01-30 13:16

    << 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 >>.

    Reading signatures

    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.

    Associativity

    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)

    • What is associativity of operators and why is it important?
    • https://www.quora.com/How-does-one-explain-the-right-to-left-associativity-of-the-conditional-operator-in-C

    Infix operator

    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.

    A word about <| 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
    
    • There is also forward version of this operator |>.
    • https://elm-community.github.io/elm-faq/#what-good-is-the--operator-if-it-is-just-function-application
    • For clarity don't mix <| and <<
    0 讨论(0)
  • 2021-01-30 13:16

    It is function composition. For your concrete example it means

    \x -> (Signal.send updateChan (toUpdate x))
    

    In elm it's not a part of the syntax but part of the standard library: Basics.<<

    0 讨论(0)
  • 2021-01-30 13:21

    << is a function composition operator, defined in core library Basics. All functions from Basics are imported into Elm projects unqualified.

    Elm's type system

    Let's recall basics of Elm type system.

    Elm is statically typed. This means that in Elm every variable or function has a type, and this type never changes. Examples of types in Elm are:

    • Int
    • String
    • Maybe Bool
    • { name : String, age : Int }
    • Int -> Int
    • Int -> String -> Maybe Char.

    Static typing means that compiler ensures types of all functions and variables are correct during compilation, so you don't have runtime type errors. In other words, you'll never have a function of type String -> String receiving or returning Int, code that allows this won't even compile.

    You can also make your functions polymorphic by replacing a concrete type such as String or Maybe Int with a type variable, which is an arbitrary lowercase string, such as a. Many Elm core functions are type polymorphic, for example List.isEmpty has the type List a -> Bool. It takes a List of some type and returns a value of type Bool.

    If you see the same type variable again, then instances of this type variable must be of same type. For example List.reverse has type List a -> List a. So if you apply List.reverse to a list of integers (i.e. to something that has type List Int), it will return a list of integers back. No way such function can take a list of integers, but return a list of strings. This is guaranteed by the compiler.

    All functions in Elm are curried by default. This means that if you have a function of 2 arguments, it is transformed into a function of 1 argument that returns a function of 1 argument. That's why you function application syntax of Elm is so different from function application in other languages such as Java, C++, C#, Python, etc. There's no reason to write someFunction(arg1, arg2), when you can write someFunction arg1 arg2. Why? Because in reality someFunction arg1 arg2 is actually ((someFunction arg1) arg2).

    Currying makes partial application possible. Suppose you want to partially apply List.member. List.member has a type a -> List a -> Bool. We can read the type as “List.member takes 2 arguments, of type a and type List a”. But we can also read the type as “List.member takes 1 argument of type a. It returns a function of type List a -> Bool”. Therefore we can create a function isOneMemberOf = List.member 1, which will have the type List Int -> Bool.

    This means that -> in type annotations of functions is right-associative. In other words, a -> List a -> Bool is the same as a -> (List a -> Bool).

    Infix and prefix notation

    Any infix operator is actually an ordinary function behind the curtains. It's just when a function name consists solely of non-alphanumeric symbols (such as $, <|, <<, etc), it is placed between 2 arguments, not in front of them (like ordinary functions).

    But you still can put a binary operator like + in front of the 2 arguments, by enclosing it in parentheses, so the 2 function applications below are equivalent:

    2 + 3 -- returns 5
    (+) 2 3 -- returns 5, just like the previous one
    

    Infix operators are just ordinary functions. There's nothing special about them. You can partially apply them just like any other function:

    addTwo : Int -> Int
    addTwo = (+) 2
    
    addTwo 3 -- returns 5
    

    Function composition

    (<<) is a function composition operator, defined in core library Basics. All functions from basics are imported into Elm projects unqualified, meaning you don't have to write import Basics exposing (..), it is already done by default.

    So just like any other operator, (<<) is just a function, like any other. What is its type?

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

    Because -> is right-associative, this is equivalent to:

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

    In other words, (<<) takes 2 functions of types b -> c and a -> b respectively, and returns a function of type a -> c. It composes 2 functions into one. How does that work? Let's look at a contrived example for simplicity's sake. Suppose we have 2 simple functions:

    addOne = (+) 1
    multTwo = (*) 2
    

    Suppose we don't have (+), only addOne, how would we create a function that adds 3, not 1? Very simple, we would compose addOne together 3 times:

    addThree : Int -> Int
    addThree = addOne << addOne << addOne
    

    What if we want to create a function that adds 2, then multiples by 4?

    ourFunction : Int -> Int
    ourFunction = multTwo << multTwo << addOne << addOne
    

    (<<) composes functions from right-to-left. But the above example is simple, because all the types are the same. How would we find a sum of all even cubes of the list?

    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
    

    (>>) is the same function, but with arguments flipped, so we can write the same composition from left to right instead:

    ourFunction2 = map cube >> filter isEven >> List.sum
    

    Recap

    When you see something like h << g << f, then you know that f, g, h are functions. When this construct h << g << f is applied to a value x, then you know:

    • Elm first applies f to x
    • then applies g to the result of the previous step
    • then applies h to the result of the previous step

    Therefore (negate << (*) 10 << sqrt) 25 equals -50.0, because you first take a square root of 25 and get 5, then you multiply 5 by 10 and get 50, then you negate 50 and get -50.

    Why << and not .

    Before Elm 0.13 (see announcement) function composition operator was (.), and its behavior was identical to current (<<). (<<) was adopted in Elm 0.13 from F# language (see Github issue). Elm 0.13 also added (>>) as equivalent to flip (<<), and (<|) as replacement for function application operator ($), and (|>) as equivalent to flip (<|).

    Infix function call

    You might be wondering if you can turn an ordinary alphanumeric function name into an infix binary operator. Before Elm 0.18 you'd use backticks to make a function infix, so below 2 would be equivalent:

    max 1 2 -- returns 2
    1 `max` 2 -- returns 2
    

    Elm 0.18 removed this feature. You can't do it in Elm anymore, but languages like Haskell and PureScript still have it.

    0 讨论(0)
  • 2021-01-30 13:21

    Explantion for javascript developers:

    --elm
    
    (a << b) x
    

    Will be similar

    //javasript
    
    a(b(x))
    

    << or >> is called function composition

    0 讨论(0)
提交回复
热议问题