Skip to content

Instantly share code, notes, and snippets.

@showell
Last active November 10, 2019 18:17
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 showell/956cc7ddf277c9679ab548a83fa79167 to your computer and use it in GitHub Desktop.
Save showell/956cc7ddf277c9679ab548a83fa79167 to your computer and use it in GitHub Desktop.

If you're coming from a language like or JavaScript (JS) or Python, you may used to be a mental model where functions (or lambdas) can actually have zero parameters. In those languages nothing gets evaluated until you invoke the functions.

zero parameters

Here's JS:

> f = function() { return 6 * 7; }
[Function: f]
> f
[Function: f]
> f()
42

Here's Python:

>>> f = lambda : 6 * 7
>>> f
<function <lambda> at 0x018103D0>
>>> f()
42

Ok, now here's Elm:

> f () = 7 * 6
<function> : () -> number
> f()
42 : number

Everything seems roughly equivalent between the three languages. In all cases there is a syntax to lazily evaluate a function/lambda without any parameters.

In Elm, is f a function with zero parameters? Or is it a one-parameter function? Well, I don't know what you call () here.

Next, let's ask if these are equivalent in Elm?

f () = 7 * 6
f = 7 * 6

The answer is they're not the same. The second f there is a value:

> f = 7 * 6
42 : number

It gets evaluated immediately. It's a value. (If you really want to think of it as a function, then 1) don't, but 2) if you must, consider it to be "eager".)

one parameter

Now let's look at one-parameter functions.

JS:

> f = function (a) { return a*2; }
[Function: f]
> f
[Function: f]
> f(5)
10

Python:

>>> f = lambda a: a*2
>>> f
<function <lambda> at 0x03A803D0>
>>> f(5)
10

And here's Elm:

> f a = a * 2
<function> : number -> number
> f
<function> : number -> number
> f(5)
10 : number

Note than in the Elm version we don't have any parentheses. Is there some profound reason why Elm's syntax differs from that of Python and JS. Not really. Elm just likes a cleaner syntax.

Mental mappings

There's a mapping from JS to Elm that looks like this:

JsToElm("function f (a,b,c,d,e) { return a+b+c+d+e; }") = "f a b c d e = a+b+c+d+e"
JsToElm("function f (a,b,c,d)   { return a+b+c+d;   }") = "f a b c d   = a+b+c+d"
JsToElm("function f (a,b,c)     { return a+b+c;     }") = "f a b c     = a+b+c"
JsToElm("function f (a,b)       { return a+b;       }") = "f a b       = a+b"
JsToElm("function f (a)         { return a;         }") = "f a         = a"
JsToElm("function f ()          { return 0;         }") = "f ()        = 0"
JsToElm("         f                    = 0"           ) = "f           = 0"

If you were to write a three-argument function in JS and mentally translate it to Elm, it goes something like this:

// JS
function f(a,b,c) = { return a+b+c; }

-- Elm
f a b c = a+b+c

Without thinking too deeply about things, you can just strip a lot of punctuation away and get equivalent Elm code. So even if you're still "thinking in JS", you can start to write Elm code.

Neither of the above functions get evaluated at page load. You have to "physically invoke" them, for lack of a better term.

// JS
f(3, 5, 7) # computes 15

-- Elm
f 3 5 7 -- computes 15

But beware of this pitfall:

# JS
# does not call some_helper_function_that_does_lotsa_stuff
function f () { return some_helper_function_that_does_lotsa_stuff(); }

-- Elm
-- DOES call some_helper_function_that_does_lotsa_stuff
f = some_helper_function_that_does_lotsa_stuff

All three languages behave similar for funtions with one or more parameters. But it's the zero-parameter case that's tricky, if you don't know the syntax.

Here Elm is actually creating a value. And this gets evaluated at page load time.

Discussion

Ok, so does any of this matter?

Well, it depends.

But let's say you have a helper function in one of your views that just has some canned text, and maybe a little helper function to draw a button:

lesson3 : Html Msg
lesson3 =
    let

        text =
            """
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            """
    in
    div []
        [ formatText text
        , nextButton GoToLesson4
        ]

You may not realize it, but the "lesson3" code will get evaluated at page time. It may look and smell like a function, but it's really a value.

Often this is actually desired behavior. If you're gonna show that component eventually anyway, why not compute it up front? Also, why compute it more than once?

90% of the time what Elm does here is the right thing.

But it may surprise you.

Let's say lesson3 is misbehaving. And you want to debug it. You might think it's only gonna get invoked when you navigate to the view that "calls" lesson3. But you'd be wrong.

Here's what you need to know:

Any definition in Elm that does not have parameters is not a function. It's a value. And it will get evaluated at page load.

If you don't like this behavior, here's the way to express that lesson3 is lazy:

lesson3 : Html Msg
lesson3 =
    let

        text =
            """
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            bla bla bla bla
            """
    in
    div []
        [ formatText text
        , nextButton GoToLesson4
        ]
@showell
Copy link
Author

showell commented Nov 10, 2019

Er, the lazy version is lesson3 () = ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment