- Hello World
- Comments
- Modules
- Tools
- HTML Embedding
- Primitives
- Collections
- Functions
- Types
- Type Aliases
- Type Annotation
- Operators
- Control Statements
- Ports
- purely functional language
- statically typed
- no runtime exceptions
- outperforms most popular rendering libraries
- package manager
- built-in tooling
- HTML, CSS, JavaScript interoperability
- clean syntax
- I like frontend again...
File HelloWorld.elm
:
import Html exposing (h1, text)
import Html.Attributes exposing (id)
-- Hello world example
main =
h1 [id "hw"] [text "Hello World!"]
-- Single line comment
{-
Multi-line comment
-}
Comments for package documentation
-- Defining a module that exports everything
module Mymodule exposing (..)
-- Export only specified entities
module Mymodule exposing (Type, value)
-- Export all or specific states of type
module Mymodule exposing
( Error(Forbidden, Timeout)
, Stuff(..)
)
type Error
= Forbidden String
| Timeout String
| NotFound String
-- qualified imports
import String -- String.toUpper, String.repeat
import String as Str -- Str.toUpper, Str.repeat
-- unqualified imports
import Mymodule exposing (..) -- Error, Stuff
import Mymodule exposing ( Error ) -- Error
import Mymodule exposing ( Error(..) ) -- Error, Forbidden, Timeout
import Mymodule exposing ( Error(Forbidden) ) -- Error, Forbidden
elm repl
/ elm-repl
Import required modules
> import List
Get function signature
> List.map
<function> : (a -> b) -> List a -> List b
> (++)
<function> : appendable -> appendable -> appendable
Elm expressions return resulting value and type
> 1 + 1
2 : number
Backslash \
is for multi-line expressions
> fn a b = \
| a + b
<function> : number -> number -> number
elm make
/ elm-make
# Default compilation
elm make HelloWorld.elm -> index.html
# Custom name
$ elm make HelloWorld.elm --output hw.js
# Multiple files
$ elm make HelloWorld.elm MyModule.elm --output hw.js
# With warnings
$ elm make HelloWorld.elm --warn
# To HTML
$ elm make HelloWorld.elm --output hw.html
elm package
/ elm-package
Installation automatically adds dependencies
in package.json
.
# Install a package
$ elm-package install evancz/elm-html
# Specific version
$ elm-package install evancz/elm-html 1.0.0
# Diff two versions
$ elm-package diff evancz/virtual-dom 2.0.0 2.1.0
Comparing evancz/virtual-dom 2.0.0 to 2.1.0...
This is a MINOR change.
------ Changes to module VirtualDom - MINOR ------
Added:
attributeNS : String -> String -> String -> VirtualDom.Property
Publishing a package requires well documented code.
module Documentation exposing
( Type
, value
, anyfinCanHappen
)
{-| Module level documentation
# Just a header, what to include below
@docs Type, value
# About Anyfin
@docs anyfinCanHappen
-}
import Random exposing (int)
{-| Type level documentation comment -}
type Type = Bool
{-|-}
-- Empty comment above
value : Int
value = 1 + 1
{-| More on anyfinCanHappen -}
anyfinCanHappen : Generator Int
anyfinCanHappen =
int 0 64
{-| This value is not exported, so isn't required -}
-- Use basic comment syntax
imNotExported =
"Don't need to comment me"
- Documentation comment starts
{-|
ends with-}
- Module documentation comes after module declaration, before the imports
- Functions are grouped into related sections by keyword
@docs <args>
and declared with Markdown - Each exported entity should have documentation comment on top of its declaration
Add README.md
otherwise publishing will fail
All packages start with initial version 1.0.0
Versions all have exactly three parts: MAJOR.MINOR.PATCH
PATCH
- the API is the same, no risk of breaking codeMINOR
- values have been added, existing values are unchangedMAJOR
- existing values have been changed or removed
elm-package.json
source-directories
- array of directories to look up for project source code inside the working directoryexposed-modules
- modules that exposed to a user after publishing, kind of interface to your internal API.
# elm-package.json
source-directories": [
".",
"SubOne",
"SubTwo"
]
"exposed-modules": [
"SubOne",
"Goodmodule",
"Module"
]
├── SubOne
│ └── SubOne.elm # Compiled and exposed
├── SubTwo
│ └── SubTwo.elm # Compiled, but unexposed
├── SubThree
│ └── SubThree.elm # Compiler won't see the source, unexposed to the users
├── README.md
├── elm-package.json
├── Goodmodule.elm # Compiled and exposed
└── Module.elm # Compiled and exposed
Publish
$ git tag -a 1.0.0 -m "initial release"
$ git push --tags
$ elm-package publish
Updating
$ elm-package bump
$ git tag -a 1.0.1 -m "secondary release"
$ git push --tags
$ elm-package publish
Running fullscreen
elm-make HelloWorld.elm
-> elm.js
<script type="text/javascript" src="elm.js"></script>
<script type="text/javascript">
Elm.Main.fullscreen();
</script>
Embed explicitly in a html element
<script type="text/javascript" src="elm.js"></script>
<script type="text/javascript">
var elmHolder = document.getElementById('hw-wrapper');
Elm.Main.embed(elmHolder);
</script>
Run without graphics
Elm.Main.worker();
Numeric types are Int
and Float
, number
represents both Int
and Float
:
> 1
1 : number
> 2.0
2 : Float
> truncate 0.1
0 : Int
> truncate 1
1 : Int
String types are Char
and String
> 'a'
'a' : Char
> "Hello"
"Hello" : String
Multi-line string
"""
Hello
World
"""
Single quotes are for char
only
> 'ab'
-- SYNTAX PROBLEM --
> "ab"
"ab" : String
> True
True : Bool
> False
False : Bool
comparable
- ints
, floats
, chars
, strings
, lists
, tuples
appendable
- strings
, lists
, text
.
Kind of dynamic types are represented as a
, b
, c
etc. meaning that you can pass any value, even functions
All values and data structures in Elm are immutable.
A list
holds a collection of related values separated by commas and enclosed in
square brackets. All the values in a list must have the same type:
> []
[] : List a
> [1,2,3]
[1,2,3] : List number
> ["a", "b", "c"]
["a","b","c"] : List String
Ways to create a list
> List.range 1 4
> [1,2,3,4]
> 1 :: [2,3,4]
> 1 :: 2 :: 3 :: 4 :: []
Tuples package many expressions into a single expression. They have a minumum of two elements and maximum of nine. The type of a tuple records the number of components and each of their types.
> (1, "2", True)
(1,"2",True) : ( number, String, Bool )
It's also possible to use commas as a tuple function, like a prefix operation.
> (,,,) 1 True 'a' []
(1,True,'a',[]) : ( number, Bool, Char, List a )
Destructuring
(x, y) = (1, 2)
> x
1 : number
A record
is a collection of key/value pairs,
similar to objects in JavaScript or dictionary in Python
myRecord =
{ style = "Blue",
number = 1,
isCool = True
}
Accessing records
> myRecord.style
"Blue" : String
> .style myRecord
"Blue" : String
Updating records returns a new record
> updatedRecord = { myRecord | style = "Red", number = 10, isCool = False }
> myRecord.style
"Blue" : String
> updatedRecord.style
"Red" : String
Destructuring
{ style, number, isCool } = myRecord
> style
"Blue" : String
Core library also has:
Basics
-- function name | arguments names = function body
sum a b = a + b
-- combine arguments in a tuple
sum (a, b) = a + b
All functions in Elm are curried by default.
If you have "a function of 2 arguments", it's really a function that takes one argument and returns a function that takes another argument:
-- Both are equal
myFunction arg1 arg2
((myFunction arg1) arg2)
-- Partial application
> minus x y = (-) x y
<function> : number -> number -> number
> minus1 = minus 1
<function> : number -> number
> minus1 11
-10 : number
Also known as lambdas
-- (\function arguments -> function body)
-- parenthesized, content starts with backslash
(\n -> n < 0)
(\x y -> x * y)
Prefix notation is when we use operators as regular functions by enclosing them in parentheses.
-- Normally you would do this
> "abcde" ++ "fghij"
"abcdefghij" : String
-- Prefix
> (++) "abcde" "fghij"
"abcdefghij" : String
Elm allows to create custom types known as union types.
The expression below creates a type which can have one of the values (or tags) from the right. Union types tightly coupled with case-of statement.
type Movement = Right | Left | Stop
Tags bring additional information, after tag itself comes a type or multiple types.
type Movement
= Right Int
| Left Int
| Stop Bool
| Coordinates (Float, Float)
-- passing to the function
myFunction ( Coordinates (45.7, 67.5) )
Union types can also have type variables
type Person a
= Name String
| Surname String
| Age Int
| About a
A Maybe
can help you with optional arguments, error handling, and records with optional fields. Think of it as a kind of null
-- Maybe resides in a module
import Maybe exposing ( Maybe(..) )
-- Takes an argument that can be filled with any value
type Maybe a = Just a | Nothing
Type annotation explicitly tells that it will give back an Int
or it won't.
getId : Int -> Maybe Int
getId id =
if id >= 0 then
Just id
else
Nothing
You can give existing types a custom name with type alias
type alias Name = String
type alias Dob = String
type alias Record = { name: Name, dob: Dob }
We can use it later annotating function
record : Record
record =
{ name = "Dave", dob = "27/08/1999" }
But still type alias
equals to it's parent type
type alias Name = String
name : Name
name =
"Dave"
secondName : String
secondName =
"Dave"
-- True
name == secondName
Elm, like most ML dialects, automatically infers most types.
-- function name : 1st arg type -> 2nd arg type -> return type
fnc : Int -> List -> Int
Example below is read as function that takes an a value and returns a b value, list of a values returns a list of b values
map: (a -> b) -> List a -> List b
Pattern matching on record fields
-- Requires the argument with x and y fields
multiply {x,y} =
x * y
Annotating records
coordinates : { x : Float, y : Float }
coordinates =
{ x = 0,
y = 0
}
In a nutshell Elm operators are functions that take two arguments.
Operator | Description | Type hint |
---|---|---|
+ |
addition | number -> number -> number |
- |
subtraction | number -> number -> number |
* |
multiplication | number -> number -> number |
/ |
floating point division | Float -> Float -> Float |
// |
integer division, discard the reminder | Int -> Int -> Int |
^ |
exponentiation | number -> number -> number |
% |
modulo | Int -> Int -> Int |
Operator | Description | Type hint |
---|---|---|
and |
bitwise AND | Int -> Int -> Int |
or |
bitwise OR | Int -> Int -> Int |
xor |
biwise XOR | Int -> Int -> Int |
Operator | Description | Type hint |
---|---|---|
== |
equal | comparable -> comparable -> Bool |
/= |
not equal | comparable -> comparable -> Bool |
< |
less than | comparable -> comparable -> Bool |
<= |
less than or equal | comparable -> comparable -> Bool |
> |
greater than | comparable -> comparable -> Bool |
>= |
greater than or equal | comparable -> comparable -> Bool |
Operator | Description | Type hint |
---|---|---|
&& |
logical and | Bool -> Bool -> Bool |
|| |
logical or | Bool -> Bool -> Bool |
not |
logical not | Bool -> Bool |
Operator | Description | Type hint |
---|---|---|
<| |
backward (pipe) function application f <| x == f x |
(a -> b) -> a -> b |
|> |
forward (pipe) function application x |> f == f x |
a -> (a -> b) -> b |
<< |
composes functions into one, arguments first applied to the function from the right side | (b -> c) -> (a -> b) -> a -> c |
>> |
same as before except arguments first applied to the function from the left side | (a -> b) -> (b -> c) -> a -> c |
Operator | Description | Type hints |
---|---|---|
++ |
put appendable things together | appendable -> appendable -> appendable |
:: |
add an element to the front of a list | a -> List a -> List a |
as |
keyword that creates aliases for values (x, y) as t == t = (x, y) |
a -> a |
All the branches of an if need to match so that no matter which one we take, we get back the same type of value overall.
if a < 1 then
"It's zero"
else
"Non-zero"
-- Multi-line.
if y > 0 then
"Greater"
else if x /= 0 then
"Not equals"
else
"silence"
Elm does not have the notion of “truthiness”.
The condition must evaluate to True or False, and nothing else.
> if 1 then "nope" else "nope again"
- TYPE MISMATCH --
Case tries to match the value of type against patterns defined after the of
keyword
type User
= Activated
| Deleted
update state =
case state of
Activated ->
-- do something
Deleted ->
-- do again
In case of passing tags with additional properties, parameters are passed along with type checking
type User
= Activated Int
| Deleted (Int, String)
update state =
case state of
Activated value ->
-- do something with value
Deleted values ->
-- do something with values
update ( Activated 1 )
update ( Deleted (0, "gone") )
let
allows you to define intermediate values.
let
x = 3 * 8
y = 4 ^ 2
in
x + y
let
helps simplify complex expressions
let
activeUsers = List.filter (\u -> u.state /= 1) model.users
in
{ model | user = activeUsers}
Ports are a general purpose way to communicate with JavaScript.
-- declare that this module uses ports
port module Main exposing (..)
-- define port
port portName : (String -> msg) -> Sub msg
var main = Elm.Main.embed(div);
// send into port
main.ports.portName.send("Port value");
port showPortName : String -> Cmd msg
function logName(name) {
console.log(name);
}
// subscribe to receive events
main.ports.showPortName.subscribe(logName);
// unsubscribe
main.ports.showPortName.unsubscribe(logName);
JavaScript | Elm |
---|---|
Booleans | Bool |
Numbers | Int, Float |
Strings | Char, String |
Arrays | List, Array, Tuples (fixed-length) |
Objects | Records |
JSON | Json.Encode.Value |
null | Maybe Nothing |