Skip to content

Instantly share code, notes, and snippets.

@showell
Created November 10, 2019 18:07
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/7d5fd44f6c809ff7d6a7df2b1067bd0c to your computer and use it in GitHub Desktop.
Save showell/7d5fd44f6c809ff7d6a7df2b1067bd0c to your computer and use it in GitHub Desktop.
Elm functions vs. Elm values
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:
~~~ js
> f = function() { return 6 * 7; }
[Function: f]
> f
[Function: f]
> f()
42
~~~
Here's Python:
~~~ python
>>> f = lambda : 6 * 7
>>> f
<function <lambda> at 0x018103D0>
>>> f()
42
~~~
Ok, now here's Elm:
~~~elm
> f () = 7 * 6
<function> : () -> number
> f()
42 : number
~~~
Everything seems roughly equivalent between the tree 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?
~~~ elm
f () = 7 * 6
f = 7 * 6
~~~
The answer is they're *not* the same. The second `f` there is a value:
~~~elm
> 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:
~~~js
> f = function (a) { return a*2; }
[Function: f]
> f
[Function: f]
> f(5)
10
~~~
Python:
~~~python
>>> f = lambda a: a*2
>>> f
<function <lambda> at 0x03A803D0>
>>> f(5)
10
~~~
And here's Elm:
~~~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 GoToLesson3
]
~~~
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 GoToLesson3
]
~~~
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment