When starting a project you will launch the next command:
elm init
You can add Elm code to src folder. For example Main.elm:
import Html main = Html.text "Hello world"
You can see its result with the next command launching the elm file.
elm reactor
After this you can create a public folder and put there the next index.html code file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MY COOL ELM APP</title>
</head>
<body>
<div id="hello-world"></div>
<script src="main.js"></script>
<script>
const myDiv = document.getElementById("hello-world");
const app = Elm.Main.init({ node: myDiv });
</script>
</body>
</html>
You can compile the elm file with:
elm make src/Main.elm --output public/main.js
A higher order function is a function that operates with functions in a way that takes a function as an argument, or returns a function.
A combinator function is a function that only relies on those functions that are passed as arguments.
A curried function is a function partly applied. For example, having this one:
getIds users = List.map (\u -> u.userId) users
We can write it like this:
getIds = List.map .userId
In this way getIds returns another function which is a map that will extract the userId paramater from what is passed to the previous function.
Free variables are those that are not taken from function arguments.
A closure is a persistent local variable scope. The scope is kept for local variables even after the code execution has moved out of that block. In the next JS code we have an outer function which will return the inner function. The variable a is defined inside outer and has that scope, however, it is accessed when inner is called.
outer = function() { var a = 1; var inner = function() { console.log(a); } return inner; // this returns a function } var fnc = outer(); // execute outer to get inner fnc();
In Elm we can achieve a closure returning a function:
> stringRepeater n = \s -> String.repeat n s <function> : Int -> String -> String > twentyTimesString = stringRepeater 20 <function> : String -> String > twentyTimesString "a" "aaaaaaaaaaaaaaaaaaaa"
let
(firstName, lastName) = ("John", "Doe")
in
lastName -- Result: Doe
-- ... or ...
second (_, snd) = snd
second (0, 1) -- Result: 1
-- ... or..
userName : User -> String
userName user =
let
(User name) = user
in
String.toLower name
-- ... or...
type alias Vector = { x : Int, y : Int }
length : Vector -> Float
length { x, y } = sqrt <| toFloat <| x^2 + y^2
length { x = 1, y = 2 } -- Result: 2.2360
length { x = 1, y = 2, z = 3 } -- Error
-- To do this:
length : { r | x : Int, y : Int } -> Float
length { x, y } = sqrt <| toFloat <| x^2 + y^2
length { x = 1, y = 2, z = 3 } -- OK
-- or
type alias Vector r = { r | x : Int, y : Int }
length : Vector r -> Float
length { x, y } = sqrt <| toFloat <| x^2 + y^2
Pattern matching is a mechanism for choosing the branch of code to execute based on the type or value of a given expression.
Constructing:
type alias User = { userId: Int, name: String }
User 1 "John"
For each record type alias, the compiler automatically generates a constructor function of the same name. The constructor is a regular function so it can be partially applied:
User 1
Extending records in the next two ways:
type alias GenericNode a = { a |
actualLoops : Int, actualRows : Int,
actualStartupTime : Float,
actualTotalTime : Float
}
type alias CteNode = GenericNode
{
nameAlias : String
}
-- A CteNode is a record with all the fields of GenericNode plus an additional alias field.
> type alias A = { a: String, b: Int }
> type alias B = { a: String, b: Int }
> a : A
| a = A "prueba" 666
{ a = "prueba", b = 666 } : A
> b : B
| b = a
{ a = "prueba", b = 666 } : B
To declare a function (type anotation) that gets a number and returns an string:
checkStatus : Int -> String
If you write “checkStatus” it will return <function> : Int -> String.
It is something like this:
<function: add> : Int -> Int -> Int | function name | type arg1 | type arg2 | type result |
Other ways to define a type:
coordinates : (Float, Float)
coordinates = (53.1201749, 8.5962037)
list : List number
list = [ 1, 2, 3, 4 ]
rect : { width : Int, height : Int }
rect = { width = 10, height = 20 }
Something similar to an enum is this:
type UserStatus = Regular | Visitor
Where UserStatus only can have values Regular or Visitor.
type UserStatus
= Regular
| Visitor
type alias User =
{ status : UserStatus
, name : String
}
thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }
Other way to the previous code:
type User = Regular String | Visitor String thomas = Regular "Thomas" kate95 = Visitor "kate95"
Lets see:
type User = Regular String Int | Visitor String -- A regular user has name and age; a visitor, only name. Regular -- <function> : String -> Int -> User Visitor -- <function> : String -> User Regular "Thomas" 44 -- Regular "Thomas" 44 : User Visitor "kate95" -- Visitor "kate95" : User
When you do not care about the type that is passed. In the next example the List passed to List.length can be a List String, List Int…
> List.length <function> : List a -> Int
However it can restrict the output. With List.reverse we know that we are going to obtain a List with the same type as it was passed on the first instance.
> List.reverse <function> : List a -> List a > List.reverse [ "a", "b", "c" ] ["c","b","a"] : List String > List.reverse [ True, False ] [False,True] : List Bool
type Tree a = Empty | Node a (List (Tree a))
Consider Set.map from the Elm core library:
map : (comparable -> comparable2) -> Set comparable -> Set comparable2 map func set =
This function produces a new set (in other words: list) from the input set by applying func to elements of set. Since set elements have to be comparable, func takes a value of a comparable type as its argument, and it also has to return a value of a comparable type.
However, the input and output types are not necessarily the same, so it doesn’t make sense to write (comparable → comparable). In order to allow these sorts of functions, number, appendable and comparable can be suffixed with an alphanumeric sequence without changing their meaning.
Those types that contain variables that are not used on their constructors:
type Unit tag value = Unit value
It can be used like this:
type KmTag = KmTag type MileTag = MileTag km : number -> Unit KmTag number km = Unit mile : number -> Unit MileTag number mile = Unit
These types use different tags. Now we can restrict value types; for example, when a function expects a KmTag and we pass a mile to it, then an error will raise.
showKm : Unit KmTag Float -> String showKm (Unit d) = String.fromFloat d distance = mile 12.34 s = showKm distance -- this raises an error
Use cases:
A type alias is a shorter name for a type. For example, you could create a User alias like this:
type alias User =
{ name : String
, age : Int
}
-- WITH ALIAS
isOldEnoughToVote : User -> Bool
isOldEnoughToVote user =
user.age >= 18
-- WITHOUT ALIAS. You would need to always manage the whole structure itself.
isOldEnoughToVote : { name : String, age : Int } -> Bool
isOldEnoughToVote user =
user.age >= 18
As I understand you could pattern match a type, but not a type alias. We specify the type of functions like update and view with type aliases.
There are two ways to insert an Elm program in your browser. As Browser.sandbox or Browser.element. The first one does not have external communication and is good to just create an Elm program that does not interlopes with the browser apart from creating html tags, the second one allows to access to REST services, time, random…
The next code is basic program structure. It uses Browser.sandbox to create the end html. Browser.sandbox receives an init value (Which is the model), an update function, and a view function.
module Marcarrones exposing (..)
import Browser
import Html exposing (..)
import Html.Events exposing (onClick)
main = Browser.sandbox { init = 0, update = update, view = view }
-- Where we create a variable Model we are creating an Int
type alias Model = Int
-- Update is a function that receives two ints and returns an int
update : Model -> Int -> Int
-- (Basic implementation) First int parameter is "inccrement", Second int is "model",
-- update increment model = model + increment
-- Adding an "if expression"
update increment model = if increment == 0 then 0 else model + increment
-- This is the standard way of a view function
view : Model -> Html Model
-- View receives a parameter Model called model; it returns an Html and the changed model
view model =
div []
[
-- A button "object" with the onClick method (onClick calls update when the button is pressed)
button [ onClick 1 ] [ text "Add" ],
div [] [ text (String.fromInt model) ],
button [ onClick -1 ] [ text "Subs" ],
div [] [
button [ onClick 0 ] [ text "Reset" ]
]
]