Elm programming language

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's import corresponds to Haskell's import qualified.[8]

import Graphics.Element exposing (..)
import Mouse     -- qualified import

main = Signal.map show Mouse.position

The value of main is displayed on screen. Function Graphics.Element.show turns any value into a displayable textual representation. Mouse.position is a value that changes over time, called a signal. Function Signal.map ensures that show 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

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 Text exposing (Text)
import Color exposing (..)
import Graphics.Element as Elem exposing (Element)  -- qualified import
 
unstyledText : Text
unstyledText = Text.fromString "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 <| Elem.justified <| styleIt unstyledText
       elem2 = Elem.width commonWidth <| Elem.centered <| styleIt <| Text.fromString "test2" 
       elem3 = Elem.width commonWidth <| Elem.rightAligned <| styleIt <| Text.fromString "test3"
       
   in Elem.flow Elem.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.[24]

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[25] 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>.[26]
  • (<~) and (~) are Elm replacements for Haskell's infix Applicative operators (<$>) and (<*>).[27]

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
  1. a b The Syntax of Elm
  2. Elm - Extensible Records
  3. Elm - Getting started with Types
  4. Wan, Zhanyong; Taha, Walid; Hudak, Paul (2002). "Event-Driven FRP". Proceedings of the 4th International Symposium on Practical Aspects of Declarative Languages: 155–172.
  5. Elm - What is Functional Reactive Programming?
  6. Attention: This template ({{cite doi}}) is deprecated. To cite the publication identified by doi:10.1145/581690.581695, please use {{cite journal}} with |doi=10.1145/581690.581695 instead.
  7. a b Elm - The Libraries You Need Automaton as an arrow implementation
  8. a b Version 0.8 release notes Section "Importing modules"
  9. About Elm Elm features
  10. Elm - JavaScript Integration
  11. a b c d Elm's Prelude
  12. Char library
  13. Text library
  14. Date library
  15. Element library
  16. a b Collage library
  17. Graphics.Input library
  18. a b c Signal library
  19. Haskell class Functor def.
  20. Haskell class Applicative def.
  21. Haskell Arrows introduction
  22. Automaton library
  23. Documentation
  24. Syntax - records
  25. JavaScript FFI
  26. Haskellwiki - Applicative functor
  27. Elm v.0.7 release notes Section: "Do you even lift?"
edit