Skip to content

Instantly share code, notes, and snippets.

@choonkeat
Last active February 18, 2020 01:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save choonkeat/28c14d2fad04281a3803ce55b07bbb97 to your computer and use it in GitHub Desktop.
Save choonkeat/28c14d2fad04281a3803ce55b07bbb97 to your computer and use it in GitHub Desktop.

I realise you could more or less guess how the first argument relates to the next arguments for certain functions was it because you've used those functions before so you remember or because it's actually guessable?

TLDR: you can tell from the function signature, but we're not used to it coming from another language

Example Json.Decode.map2

map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value

Elm doesn't care what type is a as long as when you give it all the arguments, all the a are the same. so

  • if you gave Json.Decode.string as 2nd argument
  • since it returns a Json.Decode.Decoder String value, you're saying that a should be String
  • so that first argument (a -> b -> value) now has to be (String -> b -> value)

the other way of looking at it: if you KNOW what your first argument is supposed to be e.g. (String -> Int -> User) then there's no other way: you must give a Decoder String as 2nd argument and Decoder Int as 3rd argument.... and you'll magically end up with a Decoder User as final return value.

I think what I didn't immediately see is that "of course the second and third argument is going to be fed into the first"

That is true (and we could have intuition for that) but not always the case. What's always is only that they have to line up; not what argument feeds into what other argument.

For example, Url.Parser

map : a -> Parser a b -> Parser (b -> c) c

What intuition can I have for this? I provide a first argument of type a.. and next argument of Parser a b (i'm losing my ability to understand) and then i'll get a return value of type Parser (b -> c) c???


Elm doesn't care what type is a as long as when you give it all the arguments, all the a are the same.

You can spell a type variable as anything you want as long as it starts with a lowercased letter. However, there are 3 special type variable names that carry special meaning

  • number -- if you use number as your type variable, then the type must be Int or Float. In your function body, you get to use mathematical functions on your number variables (look for things that work with number)

    myfunc : number -> number -> number
    myfunc a b =
        a * b
  • comparable -- if you use comparable as your type variable, then the type must be Int, Float, Char, String, List comparable, or Tuple comparable comparable. In your function body, you also get to use comparison functions on your comparable variables

    myfunc : comparable -> comparable -> Bool
    myfunc a b =
        a > b
  • appendable -- if you use appendable as your type variable, then the type must be String or List a. In your function body, you also get to use the ++ function

    myfunc : appendable -> appendable -> appendable
    myfunc a b =
        a ++ b

If your type signature needs multiple different number types (or different comparable or appendable), just suffix the special type variable names with numbers, e.g.

myfunc : number -> number1 -> number2 -> comparable -> comparable1

One shortcut you can use before obtaining deeper understanding is: whenever you see that a function argument is a function returning msg, i.e. (a -> msg), then you can consider it like a javascript callback function

Example 1

onAnimationFrame : (Posix -> msg) -> Sub msg

you can think of this in javascript terms like

// once I register my callback
onAnimationFrame(function(posix) {
    // on every animation frame, my function is called
    // with a `Time.Posix` value argument
    // TODO: use `posix` variable here
})

except in Elm, you don't write the code handling the callback there, instead you call onAnimationFrame in your subscriptions

subscriptions : Model -> Sub Msg
subscriptions model =
    -- once I register my callback
    onAnimationFrame FooBar

and handle it in update

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        FooBar posix ->
            -- on every animation frame, my function is called
            -- with a `Time.Posix` value argument
            -- TODO: use `posix` variable here

Example 2

-- module Http
get : { url : String, expect : Expect msg } -> Cmd msg

expectString : (Result Error String -> msg) -> Expect msg

A more complex example: we can identify a callback function argument at expectString, but in order to use that, we have to pass the entire expectString callback to the get function to perform a HTTP GET fetching a webpage

dosomething =
    -- in some function, we try to perform a http get
    Http.get
        { url = "https://elm-lang.org/assets/public-opinion.txt"
        , expect = Http.expectString GotText }

notice GotText is where our callback function is, and like the earlier example, we handle the callback in update

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        GotText result ->
            case result of
                Ok content ->
                    -- deal with the `content` variable of `String` type
                Err err ->
                    -- deal with the `err` variable of `Http.Error` type

which can be somewhat translated to javascript as

function dosomething() {
    // in some function, we try to perform a http get
    fetch('https://elm-lang.org/assets/public-opinion.txt')
        .then((response) => {
            return response.text();
        })
        .then((content) => {
            // deal with the `content` variable of `String` type
        })
        .catch((err) => {
            // deal with the `err` variable of `Http.Error` type
        });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment