More on functions
If you've followed along from the beginning, you've actually already been introduced to a dozen or so functions. It's true--all those arithmetic operators, the concatenation operator, the cons operator, and the logical operators are actually functions! They're even documented just like any other function in the Elm package site. Along with not
and toString
, they can be found in a module called Basics, which is imported by default every time you open the REPL.
Infix functions
The only way those operators differ from regular functions is that they are used primarily in infix position. That is, rather than getting placed before their arguments, they go in between them. The fundamental principle is the same--you give them arguments, and they use those arguments to return a new value.
You can use any infix function just like a normal function if you surround it in parentheses.
> (+) 2 5
7 : number
> (/) 7 2
3.5 : Float
> (::) 1 [ 2, 3, 4 ]
[1,2,3,4] : List number
> (==) "foo" "oof"
False : Bool
Just like normal functions, you can enter these parentheses-enclosed operators into the REPL and get a type signature.
> (+)
<function> : number -> number -> number
> (/)
<function> : Float -> Float -> Float
> (::)
<function> : a -> List a -> List a
> (==)
<function> : a -> a -> Bool
Notice how type variables are used in these type signatures. In the type signature of (+)
, there is only one number
type variable, with no number'
or number''
. This tells us some essential information--each number
argument and the number
being returned are the same type of number
. So we know not to try adding an Int
and Bool
.
Likewise, the use of a
in tell us all the most essential information about (::)
. This function takes a value of any type a
, plus a List
of values of that same type a
. The List
it returns will contain values all of type a
, as well.
Using a normal function in infix position
Conversely, you can use any function that takes two arguments like an infix function. Just surround it in backticks.
> 3 `String.repeat` "ha"
"hahaha" : String
> "-" `String.split` "do-re-mi"
["do","re","mi"] : List String
You'll come across cases where using this infix syntax may make your code easier to read. For now, it's just something that's good to be aware of.
Writing your own functions
Like in most programming languages, you can easily give a value a name in Elm. You do this with =
, not to be confused with the ==
equality operator.
> myNumber = 7
7 : number
> myNumber
7 : number
> myNumber * 2
14 : number
> myNumber == 7
True : Bool
When you know how to do that, you're just one step away from writing your own brand new function.
> double n = n * 2
<function> : number -> number
Here, before the =
sign, we not only named our function double
--we also gave our argument a name, n
. (We could have named this however we wanted.) Now, when you call double
with a number, like 7
, it will return the value of the expression right after =
, replacing n
with 7
.
> double 7
14 : number
You can reuse a function you write as many times as you want. It's just like any other function. As you'd expect, each time you call your function, the return value will be different depending on the arguments.
> double 2
4 : number
> double 10
20 : number
When you want your function to take multiple arguments, separate their names with spaces.
> multiply x y = x * y
<function> : number -> number -> number
> multiply 2 5
10 : number
> multiply 7 7
49 : number
As long as the expression on the right is valid Elm, you can transform your arguments however you want.
> shout message = (String.toUpper message) ++ "!"
<function> : String -> String
> shout "hello"
"HELLO!" : String
> shout "oops"
"OOPS!" : String
Curried functions
This particular concept isn't essential to a basic working knowledge of Elm, but it explains lots of the really cool behavior that makes the language stand out. So don't sweat it if this section isn't immediately clear.
Imagine you want a function that repeats a String
three times. With your knowledge already know the String.repeat
function, you could write something like this:
> repeatThree myString = String.repeat 3 myString
<function> : String -> String
But there's an easier way. Functions in Elm support partial application. That means you can call a function without all its arguments to create a new function. Here's an illustration.
> repeatThree = String.repeat 3
<function> : String -> String
String.repeat
normally takes both an Int
and a String
. But when you call it with just one argument 3
, you get a new function. Calling this new function is just like calling String.repeat
with 3
as the first argument.
> repeatThree "blah"
"blahblahblah" : String
> repeatThree "hey "
> "hey hey hey " : String
This is why functions' type signatures look the way they do. Take another look at the type signature of String.repeat
:
Int -> String -> String
If you add some parentheses, you get a visualization of what happens in partial application.
Int -> (String -> String)
Instead of a function taking two arguments, this now looks like a function that takes one argument and returns another function with the type signature String -> String
.
You could think of all Elm functions as really only taking a single argument. When you call String.repeat 3 "ha"
, that's equivalent to this two-step process:
- Passing
3
to a function of typeInt -> (String -> String)
- Passing
"ha"
immediately to the newly returnedString -> String
function.