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 | edit source]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 | edit source]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 | edit source]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.
- Records values.
- Algebraic data types (data clause) values.
Signals
[edit | edit source]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 | edit source]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 | edit source]Signal as a Haskell's Functor instance
[edit | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]- Online editor and tester: elm-lang.org/try
- Learn by example
- Elm libraries
- Predefined functions.[11]
- Offline UNIX installation with Haskell's cabal-dev
mkdir elm-compiler && cd elm-compiler
cabal-dev install elm elm-server
export PATH=$PWD/cabal-dev/bin:$PATH
Examples
[edit | edit source]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 | edit source]Styled text
[edit | edit source]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 | edit source]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 | edit source]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 | edit source]- 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 | edit source]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 | edit source]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 | edit source]- ↑ a b The Syntax of Elm
- ↑ Elm - Extensible Records
- ↑ Elm - Getting started with Types
- ↑ Wan, Zhanyong; Taha, Walid; Hudak, Paul (2002). "Event-Driven FRP". Proceedings of the 4th International Symposium on Practical Aspects of Declarative Languages: 155–172.
- ↑ Elm - What is Functional Reactive Programming?
- ↑ 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. - ↑ a b Elm - The Libraries You Need Automaton as an arrow implementation
- ↑ a b Version 0.8 release notes Section "Importing modules"
- ↑ About Elm Elm features
- ↑ Elm - JavaScript Integration
- ↑ a b c d Elm's Prelude
- ↑ Char library
- ↑ Text library
- ↑ Date library
- ↑ Element library
- ↑ a b Collage library
- ↑ Graphics.Input library
- ↑ a b c Signal library
- ↑ Haskell class Functor def.
- ↑ Haskell class Applicative def.
- ↑ Haskell Arrows introduction
- ↑ Automaton library
- ↑ Documentation
- ↑ Syntax - records
- ↑ JavaScript FFI
- ↑ Haskellwiki - Applicative functor
- ↑ Elm v.0.7 release notes Section: "Do you even lift?"