Elm (programming language)

From Wikipedia, the free encyclopedia
Jump to: navigation, search
Elm
Paradigm(s) functional reactive, functional
Designed by Evan Czaplicki
Appeared in 2011
Stable release 0.12.3 / May 20, 2014; 61 days ago (2014-05-20)
Typing discipline static, strong, inferred
Influenced by Haskell, Standard ML, OCaml, F#
License Permissive (Revised BSD) [1]
Filename extension(s) .elm
Website elm-lang.org

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]

Elm is a functional programming language in that values are immutable and functions are pure. On top of this, it add signals, which are time-varying values. Signals update in discrete events, which becomes the signal's value until another event occurs, so that signals are always defined. Any pure function can be lifted to take signal arguments and produce a signal result. Elm's standard libraries provide signals representing input, such as the mouse position or window size. There are combinators to merge, sample, and filter signals. Signals can also preserve state, updating on events to produce a new state from the old.

There is currently a single implementation of Elm, and it compiles to JavaScript and HTML, with the use of CSS styles. Signals the place of event handlers and callbacks; and all screen updates automatically. Values can be imported and exported from Javascript using ports. Most Elm programs do not manipulate the DOM directly. Rather, Element terms can be positioned relative to one another. Elements may be images, styled text, markdown, inputs, and free-form geometric drawing.

Elm may be installed locally using cabal to build from the Haskell source, or installers for Mac and Windows. The Elm website provides access to a compiler, and the program is executed immediately with hot-swapping.

Hello world[edit]

Writing hello world is extremely simple.

main = asText "Hello world"

The asText function converts almost anything into an Element that can be shown onscreen. Running this program writes "Hello world" in monospaced font in the browser. Top-level rendering to the browser is whatever main is defined to be. Here is a slightly more sophisticated program.

import Mouse
 
main : Signal Element
main = lift asText Mouse.position

Modules, such as Mouse from the standard library, are included with the import syntax. The next line is a type annotation, and is optional. The colon is pronounced as "has type", so here we indicate that "main has type Signal Element". Type annotations are checked against the inferred type and cause a type error in the case of disagreement. Additionally, main is uniquely restricted to the types Element or Signal Element. Mouse.position. is a signal of a pair of integers representing the current mouse position. Because it is a signal, applying asText to it directly will not render the mouse position. Rather it is necessary to lift the function first, to accept a signal input and produce a signal output.

Syntax and semantics[edit]

Elm is statically typed, and supports type inference and parametric polymorphism

The type system supports primitive types like integers and strings, structured data like lists, tuples, and extensible records, and custom ADTs.[2] All of these values are immutable, unless placed into signals. Signals of signals are invalid for technical reasons.

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

Signals[edit]

Signals are value-varying items and have types such 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)[5] as in

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

Events[edit]

Void events have type Signal ()

-- from Graphics.Input
button : Handle a -> a -> String -> Element
 
-- from the Mouse library 
clicks : Signal ()  -- triggers on every mouse click

Events with information (e.g. keyboard events) are handled as a signal of its state (lastPressed instead of keyPress).

-- from the Keyboard library
lastPressed : Signal KeyCode
--
ctrl : Signal Bool

Ajax[edit]

-- from the Http library
send : Signal (Request a) -> Signal (Response String)
-- 
data Response a = Success a | Waiting | Failure Int String

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.[6]

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

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][8]

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:[9]

-- 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.[10]

  • 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 (..) is used for namespace inclusion.[12]

Non-varying[edit]

Styled text[edit]

-- elm v. 0.12
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.leftAligned  <| 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.[13]

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[14] gives the opportunity 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') ;
var myModule = Elm.ElmMain ;
Elm.embed(myModule, 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>.[15]
  • (<~) and (~) are Elm replacements for Haskell's infix Applicative operators (<$>) and (<*>).[16]

Tictac varying graphics[edit]

Using Graphics.Collage library.[17]

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
-}

Input / Controls One-to-Many relationship[edit]

-- elm v. 0.12
import Graphics.Input as I
 
data Keys = Digit Int | Plus | Total | ClearDigit | ClearAcc
 
-- multi-control input
keysController : I.Input Keys
keysController = I.input ClearAcc
 
calcInterface : Element
calcInterface =
  flow down [  
    flow right [ I.button keysController.handle (Digit 1) "1"
               , I.button keysController.handle (Digit 2) "2"
               , I.button keysController.handle (Digit 3) "3"
               , I.button keysController.handle (Digit 0) "0"
               ],
 
    flow right [ I.button keysController.handle    Plus    "+"
               , I.button keysController.handle    Total   "="
               , I.button keysController.handle  ClearDigit "C"
               , I.button keysController.handle  ClearAcc  "AC"
               ]
    ]   
 
calcModel : Keys -> (Int,Int) -> (Int,Int)
calcModel k state = 
   let (disp, acc) = state   -- (display, accumulator)
   in case k of
         ClearAcc -> (0,0)
         ClearDigit -> (disp `div` 10, acc)
         Digit n -> (disp * 10 + n, acc)
         Plus -> (0, acc + disp)
         Total -> (acc, acc)
 
-- using past-dependent fold function Signal.foldp
-- Signal.foldp : (a -> b -> b) -> b -> Signal a -> Signal b
 
sigState = foldp calcModel (0,0) keysController.signal   -- past-dependent fold
 
sigDisplay = lift (asText . fst) sigState
 
main = lift (below calcInterface) sigDisplay

Password double field retype checker[edit]

With color changing submit button.

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

-- elm v.0.12
import Graphics.Input as I
import Graphics.Input.Field as F
import Graphics.Element as Elem
import String as S
 
-- button color modifier (pointfree)
passwdOkColour : F.Content -> F.Content -> Element -> Element
passwdOkColour contPasswd1 contPasswd2 = 
  let passwd1 = contPasswd1.string
      passwd2 = contPasswd2.string
  in    
    if S.length passwd1 >= 6 && passwd1 == passwd2
    then Elem.color green 
    else Elem.color red
 
prependLabel = beside . plainText
 
controllerPasswd : I.Input F.Content
controllerPasswd = I.input F.noContent
 
sigElmPasswd : Signal Element
sigElmPasswd = F.password F.defaultStyle controllerPasswd.handle id "Type password (min. 6 characters)!" 
                          <~ controllerPasswd.signal
 
controllerPasswdRetype : I.Input F.Content
controllerPasswdRetype = I.input F.noContent
 
sigElmPasswdRetype : Signal Element
sigElmPasswdRetype = F.password F.defaultStyle controllerPasswdRetype.handle id "Retype password!" 
                          <~ controllerPasswdRetype.signal
 
controllerSubmit : I.Input (Maybe ())
controllerSubmit = I.input Nothing  -- default
 
elmSubmit : Element
elmSubmit = I.button controllerSubmit.handle (Just ()) "Submit"
 
dynamicElement : Signal Element
dynamicElement = 
    let labeledField1Signal = lift (prependLabel "passwd: ") sigElmPasswd
 
        labeledField2Signal = lift (prependLabel "control: ") sigElmPasswdRetype
 
        coloredButSignal = passwdOkColour <~ controllerPasswd.signal 
                                           ~ controllerPasswdRetype.signal
                                           ~ constant elmSubmit
 
        elemSignals = [labeledField1Signal, labeledField2Signal, coloredButSignal]
 
    in (flow down) <~ combine elemSignals
 
main = dynamicElement

References[edit]

External links[edit]