Functions
I thought that it would be good for us to start our exploration of Elm with functions. Generally, programs consist of Data and Behavior (Nouns and Verbs). In programs behavior is primarily represented as Functions and the data is modeled/described using Types/DataStructures. (In object oriented languages we have classes that combine data and behavior, but that is not a pattern that you use heavily in pure Functional Languages).
Anyway it is more fun to get started with functions first. This will enable you to write some working programs before you start to work on getting a deeper understanding of Types. If you want to reverse the order just jump ahead to the section on Types and then come back to this section.
Here are some of the obvious components of functions:
Function Signature
This function signature consists of:
- Function Name: Here we are looking at named functions. (You will encounter Lambda Functions later that do not have names.)
- Parameters: All functions have a finite set of Parameters
- Return values: The value that the function returns. Note: Elm, does not require an explicit use of the return keyword! The result of evaluating the last expression is returned.
Let us look at these definitions in terms of the function isEven
that we have defined in the example below. The function isEven
takes an Integer (Int) and returns True or False (Bool) depending on whether it is even or not.
Function Signatures and Type Inference
The type signature of the function isEven
defined below is: isEven : Int -> Bool
. You can read this as isEven is a function that takes an Int and returns a Bool
or isEven has type Int goes to Bool
. But we do not have to declare the signature of the function the Elm compiler will infer it.
What about functions with many parameters? The last type in the signature is the type of the return value and the rest are input parameters.
It is always good practice to figure out the signature of the functions that we are creating and declare it. This way the compiler will compare the inferred signature and the declared signature and let you know if they do not match. This will enable us to eliminate a lot bugs in our programs at compile time.
Side effects and Pure Functions
A pure function is a function that does not change anything outside its definition or depend on the state/value of anything aside from what is passed in as parameters. This also means that if the function is invoked with the same parameters it always has the same result. This style of functional programming with "pure functions" eliminates a large class of bugs where we have unintentionally modified something that exists outside our function. Additionally when we want to make our concurrent and run on multiple threads, our programs are easier to reason about and less prone to error. Since we cannot end up modifying the same object from different threads which is a major source of bugs (race conditions/deadlocks)! Elm encourages side effect free programming but gives us tools to introduce side effects in a controlled manner. (More on all this later.)
Okay, having said all that let us look at some functions:
import Graphics.Element exposing (..)
-- a simple function to check if n is even
isEven : Int -> Bool
isEven n = n % 2 == 0
--defining a simple function that squares numbers
sqr : Int -> Int
sqr n =
n*n
-- convert temperature in Centigrade to Fahrenheit
centigradeToFahrenheit : Float -> Float
centigradeToFahrenheit c =
c * 9/5 + 32
add : Int -> Int -> Int
add a b =
a + b
increment : Int -> Int
increment n = n+1
main = flow down [print "The sqr of 5 is : " (sqr 5)
,print "40 degrees centigrade in Fahrenheit is : " (centigradeToFahrenheit 40)
,print "Is 5 an even number : " (isEven 5)
,print "3 + 2 is : " (add 3 2)
,print "incrementing 5 we get : " (increment 5)
]
--a helper function to make display easier
print : String -> a -> Element
print message value = show (message ++ (toString value))
Two comments:
Notice that the signature does not actually contain the names of the parameters but the
Type
of the parameters. We will cover types in detail a little later. I wanted you to get started with writing some code and having some fun, before we do more theory about types. In principle at this time you can make functions and just let the compiler do the type inferencing till you finish reading the section on Types.To display the output of the functions we are using the Graphics package and the function
show
defined there. I have created a simple helper called print that you can use, it takes a string (message) and the output of your functions (value) and displays it. Again we will explain all this later. Also, since we have multiple prints in the code, we put them in a list and then we have to include the functionflow
to display the list correctly. I do not expect you to understand all this. But I just want you to know at this time that there is no magic, it can all be explained and worked out. And we will do this in subsequent chapters. For now I think you should be able to use this code as a template and make changes to accommodate new functions and see their output.main : This is the place where Elm starts when it runs your code for you.
Okay time to make some functions of your own.
Exercises
Create the functions
subtract
,divide
,multiply
just like we did theadd
function.Create a function to convert from kilograms to pounds.
Slightly more challenging create a function to check if a string is a palindrome. (Hint: there is a function called
reverse
that you can use. In case you are stuck you can see the solution below.)
Solutions
import Graphics.Element exposing (..)
import String exposing (..)
kgsToPounds : Float -> Float
kgsToPounds valueInKgs =
2.2046* valueInKgs
--defining the function is Palindrome
isPalindrome : String -> Bool
isPalindrome aString =
aString == reverse aString
main = flow down [print " 5 kgs in pounds is : " (kgsToPounds 5)
,print "Testing for Palindrome : " (isPalindrome "madamimadam")
]
--a helper function to make display easier
print message value = show (message ++ (toString value))