Elm programming language

From Wikibooks, open books for an open world
Jump to: navigation, search

Elm is a functional programming language for declaratively creating web browser based graphical user interfaces.

Elm uses the Functional Reactive Programming style and purely functional graphical layout to build user interface without any destructive updates.

Description[edit]

The primary implementation of Elm compiles to JavaScript and HTML, with the use of CSS styles.

In Elm, Functional Reactive Programming takes the place of event handlers and callbacks; it also manages all screen updates automatically. Purely functional graphical layout takes the place of working with the DOM. Elm also allows Markdown to be embedded directly.

Syntax and Semantics[edit]

Elm adopts a Haskell styled syntax, with influences from OCaml and FSharp. For example, "has type" is written with a single colon (:), and forward and backward function application use the (<|) and (|>) operators[1] where (f x) equals (f <| x) equals (x |> f).

Elm has extensible records[2] which safely provide much of the flexibility of Javascript's object model.

The type system supports primitive types like integers and floats, structured data like tuples and records, and custom ADT's.[3]

Elm's version of Functional Reactive Programming is event-driven, meaning that updates are only performed as necessary. It shares the closest resemblance to Event-Driven FRP[4][5] and Arrowized FRP.[6][7]

The following program displays the position of the mouse as it moves around the screen, automatically updating the screen in real-time. Note: Elm import correspond to Haskell's import qualified.[8]

import Mouse     -- qualified import
 
main = lift asText Mouse.position

The value of main is displayed on screen. Function asText turns any value into a displayable textual representation. Mouse.position is a value that changes over time, called a signal. Function lift ensures that asText is applied to Mouse.position every time the mouse moves.

Elm has a small but expressive set of language constructs, including if-expressions, let-expressions, case-expressions, anonymous functions, and list interpolation.[1][9]

Elm also has both a module system and a foreign function interface for JavaScript.[10]

The predefined functions are in the Prelude module.[11]

Values[edit]

Values are immutable:

  • Bool, Int, Float.[11]
  • Char,[12] String,[11] (styled) Text.[13]
  • Time, Date.[14]
  • Element (html structural elements).[15]
  • graphic representations (called Forms), shapes and paths.[16]
toForm : Element -> Form

>> toForm turns any Element into a Form. This lets you use text, gifs, and video in your collage. This means you can move, rotate, and scale an Element however you want.

Signals[edit]

Signals are value varying items and have types as (Signal value_type).

They include time varying items (also called behaviors) and event driven sources.

-- current time, updated every t (from Time lib)
every : Time -> Signal Time
 
-- current mouse position (from Mouse lib)
position : Signal (Int,Int)

Html form input elements, may vary in style and state. Their generator functions mostly return a pair (element signal, status signal)[17] as in

-- create a checkbox with a given start state.
checkbox : Bool -> (Signal Element, Signal Bool)

Dependent signals[edit]

Dependent signals are like formula cells in a spreadsheet. They may react to updates of their operand signals.

The one defined as main starts the scanning of the dependency/reaction directed graph to find the independent signal variables for inclusion in the source event loop.

You define signal formulas by using signal filter functions, or by applying lifted value functions to previously defined signals. To apply a function of N parameters, you have to lift the type of a values function to a signals function, either through the lift<N> function, or by applying lift to the function, followed by signal applicative terms as ((~) signal) for functional parameters application exposed below.[18]

You can use a value in a signal position by lifting its type through the Signal.constant function.

From a Haskell perspective[edit]

Signal as a Haskell's Functor instance[edit]

See Functor class def.[19] From Elm's Signal library:[18]

-- lift a ''values'' function type to a ''signals'' function one
 
-- similar to Haskell's ''fmap'' and ''Control.Applicative.liftA''
lift : (a -> b) -> Signal a -> Signal b
 
-- (<~) is an alias for lift
Signal as a Haskell's Applicative instance[edit]

See Applicative class def.[20] From Elm's Signal library:[18]

-- lift a Value
constant : a -> Signal a
 
-- signal application
(~) : Signal (a -> b) -> Signal a -> Signal b
 
-- sample from the second input every time an event occurs on the first input
-- like Haskell's Control.Applicative.(*>) sequence actions discarding the first result
sampleOn : Signal a -> Signal b -> Signal b
 
----
-- lift<N>: to lift a function of N value parameters (defined lift2 to lift8)
-- similar to Haskell's Control.Applicative.liftA<N> 
result_signal = liftN fun_N_ary signal1 signal2 ... signalN
 
-- equivalent with binary infix operators
result_signal = fun_N_ary <~ signal1 ~ signal2 ~ ... ~ signalN

Composable signal transformers. Automatons[edit]

This is used to generate signal transformers as a chain.

Individual chain links, with type (Automaton input output) behave like computation (side effect) functions with only one parameter.

To chain two of them the input type of the follower must match the output result type of the precedent.

This concept is borrowed from Haskell's arrows (effects sequencing through chaining of morphisms).[7][21]

You may apply them to a Signal with the Automaton.run library function, specifying the Automaton and a default result for the case of lack of input value.

From Elm's Automaton library:[22]

-- generator from a pure mapping function
pure : (a -> b) -> Automaton a b
 
-- generators from an initial state and a function of input and state
state : b -> (a -> b -> b) -> Automaton a b   
hiddenState : s -> (a -> s -> (s,b)) -> Automaton a b
 
-- automaton application
run : Automaton a b -> b -> Signal a -> Signal b
 
result_signal = run myAutomaton result_default input_signal
 
 
-- compose two automatons, chaining them together.
(>>>) : Automaton a b -> Automaton b c -> Automaton a c

Containers[edit]

See ref.[23]

  • List, Set, Dict
  • Maybe (for optional parameters, and partially defined function results, as Just v or Nothing)
  • Either (error aware results, as Right correct_result or Left error)

To process a list of signals as one:

-- combine a list of signals into a signal of their value list 
Signal.combine : [Signal a] -> Signal [a]

Tools[edit]

mkdir elm-compiler && cd elm-compiler
cabal-dev install elm elm-server
 
export PATH=$PWD/cabal-dev/bin:$PATH

Features not in Elm[edit]

  • they are listed at the ref.[24]

Examples[edit]

Imported module members should be used qualified, except if listed at import module (members...) or when import open is used for namespace inclusion.[8]

Non-varying[edit]

Styled text[edit]

import Graphics.Element as Elem  -- qualified import
 
unstyledText : Text
unstyledText = Text.toText "test1"
 
styleIt : Text -> Text
styleIt = (Text.typeface ["serif"]) . (Text.color red) 
 
-- (f <| x = f x), used to avoid parentheses
 
alignTest : Int -> Element
alignTest commonWidth = 
   let elem1 = Elem.width commonWidth <| Text.justified <| styleIt unstyledText
       elem2 = Elem.width commonWidth <| Text.centered <| styleIt <| Text.toText "test2" 
       elem3 = Elem.width commonWidth <| Text.rightAligned <| styleIt <| Text.toText "test3"
 
   in flow down [elem1, elem2, elem3]   
 
main : Element
main = alignTest 200

You may try it in the online editor/compiler/executor.

Polymorphism on Record types[edit]

See records.[25]

module MyModule where
 
import Color
 
type Named a = { a | name : String }  -- records with a ''name'' field
 
getName : Named a -> String
getName {name} = name
 
dude = {name="John Doe", age=20}
lady = {name="Jane Doe", eyesColor = Color.blue}
 
names : [String]
names = [ getName dude, getName lady]
 
fullData = [show dude, show lady]
 
staticElement : Element
staticElement = flow down <| map plainText
                                 ["Names: " ++ show names
                                 , show fullData
                                 ]
main = staticElement
  • embedding it in a div element
<!DOCTYPE HTML>
<!-- MyModule.html -->
<html>
<head><meta charset="UTF-8">
  <title>MyModule</title>
  <!-- elm-runtime.js and js compiled modules -->
  <script type="text/javascript" 
          src="/your-install-directory/cabal-dev/share/Elm-N.N.N.N/elm-runtime.js"></script>
  <script type="text/javascript" src="MyModule.js"></script>
</head>
<body>
  <div id="myId" style="width:100;height:100;"></div><!-- Elm container must be a "div" element and must be empty -->
  <script type="text/javascript">
 
var myContainer = document.getElementById('myId') ; 
 
Elm.embed(Elm.MyModule, myContainer) ;
  </script>
  <noscript>Javascript is not enabled</noscript>
</body></html>
  • compile and test offline
# compile to Javascript
$ elm --make -s --only-js MyModule.elm
# run
$ browser MyModule.html

Parameterizing an Elm script[edit]

The port FFI (Foreign function interface with JavaScript) feature[26] gives the oportunity to supply parameters at the html level.

module ElmMain where
 
port arg1 : String
port arg2 : Int
port arg3 : [String]
 
implode : [String] -> String
implode = concat . intersperse ", "
 
main = asText <| implode [arg1, show arg2, implode arg3]
<!DOCTYPE HTML>
<html>
<head><meta charset="UTF-8">
  <title>Title</title>
  <!-- elm-runtime.js and js compiled modules 
      (if compiled with --make the ElmMain.js contains also the possibly imported user modules) -->
 
  <script type="text/javascript" 
          src="/your-install-directory/cabal-dev/share/Elm-N.N.N.N/elm-runtime.js"></script>
  <script type="text/javascript" src="ElmMain.js"></script>
</head>
<body>
  <div id="myId" style="width:100;height:100;"></div><!-- Elm container must be a "div" element and must be empty -->
  <script type="text/javascript">
 
var myPorts = {arg1: "do re mi",   // after "The Jackson five" "abc" lyrics
               arg2: 123,
               arg3: ["abc", "you and me"]  
               } ;
 
var myContainer = document.getElementById('myId') ; 
 
Elm.embed(Elm.ElmMain, myContainer, myPorts) ;
  </script>
  <noscript>Javascript is not enabled</noscript>
</body></html>

Signals (varying) examples[edit]

  • lift: is like Haskell's fmap for Signals
lift : (a -> b) -> Signal a -> Signal b
  • lift<N> : applies a function of N values to N Signals; it acts like Haskell's Applicative liftA<N>.[27]
  • (<~) and (~) are Elm replacements for Haskell's infix Applicative operators (<$>) and (<*>).[28]

Tictac varying graphics[edit]

Using Graphics.Collage library.[16]

myShape1 : Shape
myShape1 = circle 30
myShape2 = rect 60 60
 
myForm1 : Form
myForm1 = outlined (dashed green) myShape1
myForm2 = outlined (solid red) myShape2
 
forms : [Form] 
forms = [myForm1 |> move (10, -10)
                       , myForm2 |> move (30, -30) 
                                 |> rotate (degrees 45)
                                 |> scale 1.5
 
                       , plainText "mytext"
                                 |> toForm
                                 |> move (20, -20)
                                 |> rotate (degrees 30)
                                 |> scale 2
                       ]
 
mapRotate : Float -> [Form] -> [Form]
mapRotate t = let f = rotate <| degrees <| t * 10
              in map f
 
-- let's define the left-to-right function composition operator
f >> g = g . f
 
-- time signal in truncated seconds
tictac : Signal Float
tictac = let f = inSeconds >> truncate >> toFloat
         in every (2 * second) 
              |> lift f
 
main : Signal Element
main = constant forms
          |> lift2 mapRotate tictac
          |> lift3 collage (constant 200) (constant 200)
 
-- equivalent with (<~) and (~) infix operators
main = let signal1 = mapRotate <~ tictac ~ constant forms
       in collage <~ constant 200 ~ constant 200 ~ signal1
 
-- equivalent using ''constant'' to lift function types
main = let signal1 = constant mapRotate ~ tictac ~ constant forms
       in constant collage ~ constant 200 ~ constant 200 ~ signal1

Password double field retype checker[edit]

With color changing submit button.

import Graphics.Input as Input
import Graphics.Element as Elem
 
-- button color modifier (pointfree)
passwdOkColour : String -> String -> Element -> Element
passwdOkColour passwd1 passwd2 = 
  if passwd1 == passwd2 && length passwd1 >= 6 
     then Elem.color green 
     else Elem.color red
 
display field1Elem field2Elem submitButtonElem =
  field1Elem `above` field2Elem `above` submitButtonElem
 
dynamicElement : Signal Element
dynamicElement = 
       let (field1ElemSignal, fld1StateSignal) = Input.password "Type password (min. 6 characters)!"
           labeledField1Signal = let prependLabel = beside (plainText "passwd: ")
                                 in lift prependLabel field1ElemSignal
 
 
           (field2ElemSignal, fld2StateSignal) = Input.password "Retype password!"
           labeledField2Signal = let prependLabel = beside (plainText "control: ")
                                 in lift prependLabel field2ElemSignal
 
           (submitButton, pressedSignal) = Input.button "Submit"
 
           coloredButSignal = constant submitButton 
                                 |> lift3 passwdOkColour fld1StateSignal fld2StateSignal 
 
       in  lift3 display labeledField1Signal labeledField2Signal coloredButSignal
 
main = dynamicElement

Note, as of Elm 0.10 Strings are no longer lists of characters, so the above becomes something more like this:

import Graphics.Input as Input
import Graphics.Element as Elem
import String as S
 
-- button color modifier (pointfree)
passwdOkColour : String -> String -> Element -> Element
passwdOkColour passwd1 passwd2 = 
    if S.length passwd1 >= 6 && passwd1 == passwd2
    then Elem.color green 
    else Elem.color red
 
display field1Elem field2Elem submitButtonElem =
    field1Elem `above` field2Elem `above` submitButtonElem
 
prependLabel = beside . plainText
 
dynamicElement : Signal Element
dynamicElement = 
    let (field1ElemSignal, fld1StateSignal) = Input.password "Type password (min. 6 characters)!"
        labeledField1Signal = lift (prependLabel "passwd: ") field1ElemSignal
        (field2ElemSignal, fld2StateSignal) = Input.password "Retype password!"
        labeledField2Signal = lift (prependLabel "control: ") field2ElemSignal
        (submitButton, _) = Input.button "Submit"
        coloredButSignal = constant submitButton 
                         |> lift3 passwdOkColour fld1StateSignal fld2StateSignal 
    in lift3 display labeledField1Signal labeledField2Signal coloredButSignal
 
main = dynamicElement

References[edit]

External links[edit]