Functions are an important building block in Elm. In this lesson we will review stateless functions, function composition, anonymous functions, Currying, and more.
Our app right now does one thing, writes "Hello, world" to our page. Let's expand our app and check out functions.
Let's create a view function to hold what's currently in main, create a name for the function, then equals. We could add our new line and a tab. Now move the body of main to the view.
Technically, view is not a function. It's just a value. To have a function, it should accept at least one parameter. Let's add one so message can be passed in. Input parameters are separated by space. We pass params into the function the same way, separating with a space. Verify the output is still the same.
At closer inspection of the view, we notice that it's operating only on the inputs given to it. The outputs will always be the same based on the inputs. This is called a stateless function. Put simply, a stateless function given the same inputs always returns the same outputs and must not mutate anything.
All functions in Elm are stateless, and this removes the possibility for a host of bugs that can be introduced within our app. They also make troubleshooting much easier because you can look at the inputs and outputs to see what a function is doing. We could make this troubleshooting even easier by providing a type annotation.
A type annotation is a one liner that can show us the inputs and outputs of a function. We start by typing the name of our function, then a colon. Now we start listing the types of parameters. In this case, we're expecting a string. We could add our return type. Since the last expression of a function is always return, the last type in an annotation is the return type.
We also now need to import the HTML type. Verify that works.
Elm is statically typed, so the compiler will guarantee that the types passed into a function match what the function will handle so that the compiler can check the type errors that we might not discover until run time in other languages. At build time, the compiler will also infer your types.
Type annotations are optional, but using type annotations really provide a lot of help to us and other developers that have to read our code.
Keep in mind that just because they're optional does not mean they're ignored at build time. Let's change our annotation to expect an end. We receive a type mismatch. The compiler has inferred that we're expecting a string, but we've stated an end.
Let's expand on our view function and play with function composition. We can include the string package. Let's make our "Hello, world" all upper case.
If we save this, we find out we have a slight problem. Since parameters are separated by space, text only takes one input. It thinks we're trying to pass in two. We need to change the order in which these are evaluated. There are a few ways to do this, for example, with parenthesis.
Let's add another function. Notice the order of evaluation. We read inside out to understand what's happening. It might help to read this from top to bottom. We can use the forward function to lay out a series of functions starting with our string. Let's verify that works.
The order is evaluated top down, so the string is evaluated, then the result is passed into two upper. Once the string is uppercase, it will be passed into repeat. Finally, the result will be passed into text and returned from our function. No repeat in the series of functions. Its annotation would look like this.
We see that it takes an end. Since we only provide the first value, it returns a function instead of a value. When two upper is evaluated, the message is being passed in. Finally, a value is returned and passed to the next line. This is known as currying. This allows us to call a function providing only the parameters that we have. Then later, we can provide the rest of the parameters. This really helps us to build new functions from others.
For example, let's replace repeat with a function of our own creation called triple.
Let's verify that works. We created a new function by providing only the first parameter for repeat. Also our triple function is carried automatically.
Let's take a look again in view and look how we're composing our functions. What I really like about this syntax is we can easily add a new function anywhere within the chain.
Let's add an anonymous function to add a comma and a space between each repetition. Anonymous functions begin with a back slash. We define s to take the value being passed in from the previous line. We use the double plus or string concatenation operator to add a comma and a space. When we run that, we see "Hello, world" separated with comma and spaces.
As you can see, functions are extremely important building blocks in Elm.