Elm is afunctional programming language fordeclaratively creatingweb browser basedgraphical user interfaces.
Elm uses theFunctional Reactive Programming style andpurely functional graphical layout to build user interface without any destructive updates.
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 allowsMarkdown to be embedded directly.
Elm adopts aHaskell styled syntax, with influences fromOCaml andFSharp. 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 ofJavascript's object model.
The type system supports primitive types like integers and floats, structured data like tuples and records, and customADT's.[3]
Elm's version ofFunctional 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'simport corresponds to Haskell'simport qualified.[8]
importGraphics.Elementexposing(..)importMouse-- qualified importmain=Signal.mapshowMouse.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 thePrelude module.[11]
Values are immutable:
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 are value varying items and have types as(Signal value_type).
They include time varying items (also calledbehaviors) and event driven sources.
-- current time, updated every t (from Time lib)every:Time->SignalTime-- 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->(SignalElement,SignalBool)
Dependent signals are likeformula cells in aspreadsheet. They may react to updates of their operand signals.
The one defined asmain starts the scanning of the dependency/reactiondirected graph to find the independent signal variables for inclusion in the source event loop.
You define signalformulas 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 avalues function to asignals function, either through thelift<N> function, or by applyinglift 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 theSignal.constant function.
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)->Signala->Signalb-- (<~) is an alias for lift
See Applicative class def.[20] From Elm's Signal library:[18]
-- lift a Valueconstant:a->Signala-- signal application(~):Signal(a->b)->Signala->Signalb-- sample from the second input every time an event occurs on the first input-- like Haskell's Control.Applicative.(*>) sequence actions discarding the first resultsampleOn:Signala->Signalb->Signalb------ lift<N>: to lift a function of N value parameters (defined lift2 to lift8)-- similar to Haskell's Control.Applicative.liftA<N>result_signal=liftNfun_N_arysignal1signal2...signalN-- equivalent with binary infix operatorsresult_signal=fun_N_ary<~signal1~signal2~...~signalN
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 fromHaskell's arrows (effects sequencing through chaining ofmorphisms).[7][21]
You may apply them to aSignal with theAutomaton.run library function, specifying theAutomaton and a default result for the case of lack of input value.
From Elm's Automaton library:[22]
-- generator from a pure mapping functionpure:(a->b)->Automatonab-- generators from an initial state and a function of input and statestate:b->(a->b->b)->AutomatonabhiddenState:s->(a->s->(s,b))->Automatonab-- automaton applicationrun:Automatonab->b->Signala->Signalbresult_signal=runmyAutomatonresult_defaultinput_signal-- compose two automatons, chaining them together.(>>>):Automatonab->Automatonbc->Automatonac
See ref.[23]
To process a list of signals as one:
-- combine a list of signals into a signal of their value listSignal.combine:[Signala]->Signal[a]
mkdirelm-compiler&&cdelm-compilercabal-devinstallelmelm-serverexportPATH=$PWD/cabal-dev/bin:$PATH
Imported module members should be used qualified, except if listed atimport module (members...) or whenimport open is used for namespace inclusion.[8]
importTextexposing(Text)importColorexposing(..)importGraphics.ElementasElemexposing(Element)-- qualified importunstyledText:TextunstyledText=Text.fromString"test1"styleIt:Text->TextstyleIt=(Text.typeface["serif"])>>(Text.colorred)-- (f <| x = f x), used to avoid parenthesesalignTest:Int->ElementalignTestcommonWidth=letelem1=Elem.widthcommonWidth<|Elem.justified<|styleItunstyledTextelem2=Elem.widthcommonWidth<|Elem.centered<|styleIt<|Text.fromString"test2"elem3=Elem.widthcommonWidth<|Elem.rightAligned<|styleIt<|Text.fromString"test3"inElem.flowElem.down[elem1,elem2,elem3]main:Elementmain=alignTest200
You may try it in theonline editor/compiler/executor.
See records.[24]
moduleMyModulewhereimportColortypeNameda={a|name:String}-- records with a ''name'' fieldgetName:Nameda->StringgetName{name}=namedude={name="John Doe",age=20}lady={name="Jane Doe",eyesColor=Color.blue}names:[String]names=[getNamedude,getNamelady]fullData=[showdude,showlady]staticElement:ElementstaticElement=flowdown<|mapplainText["Names: "++shownames,showfullData]main=staticElement
<!DOCTYPE HTML><!-- MyModule.html --><html><head><metacharset="UTF-8"><title>MyModule</title><!-- elm-runtime.js and js compiled modules --><scripttype="text/javascript"src="/your-install-directory/cabal-dev/share/Elm-N.N.N.N/elm-runtime.js"></script><scripttype="text/javascript"src="MyModule.js"></script></head><body><divid="myId"style="width:100;height:100;"></div><!-- Elm container must be a "div" element and must be empty --><scripttype="text/javascript">varmyContainer=document.getElementById('myId');Elm.embed(Elm.MyModule,myContainer);</script><noscript>Javascript is not enabled</noscript></body></html>
# compile to Javascript$elm--make-s--only-jsMyModule.elm# run$browserMyModule.html
Theport FFI (Foreign function interface withJavaScript) feature[25] gives the opportunity to supply parameters at thehtml level.
moduleElmMainwhereportarg1:Stringportarg2:Intportarg3:[String]implode:[String]->Stringimplode=concat.intersperse", "main=asText<|implode[arg1,showarg2,implodearg3]
<!DOCTYPE HTML><html><head><metacharset="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) --><scripttype="text/javascript"src="/your-install-directory/cabal-dev/share/Elm-N.N.N.N/elm-runtime.js"></script><scripttype="text/javascript"src="ElmMain.js"></script></head><body><divid="myId"style="width:100;height:100;"></div><!-- Elm container must be a "div" element and must be empty --><scripttype="text/javascript">varmyPorts={arg1:"do re mi",// after "The Jackson five" "abc" lyricsarg2:123,arg3:["abc","you and me"]};varmyContainer=document.getElementById('myId');Elm.embed(Elm.ElmMain,myContainer,myPorts);</script><noscript>Javascript is not enabled</noscript></body></html>
lift:(a->b)->Signala->Signalb
Using Graphics.Collage library.[16]
myShape1:ShapemyShape1=circle30myShape2=rect6060myForm1:FormmyForm1=outlined(dashedgreen)myShape1myForm2=outlined(solidred)myShape2forms:[Form]forms=[myForm1|>move(10,-10),myForm2|>move(30,-30)|>rotate(degrees45)|>scale1.5,plainText"mytext"|>toForm|>move(20,-20)|>rotate(degrees30)|>scale2]mapRotate:Float->[Form]->[Form]mapRotatet=letf=rotate<|degrees<|t*10inmapf-- let's define the left-to-right function composition operatorf>>g=g.f-- time signal in truncated secondstictac:SignalFloattictac=letf=inSeconds>>truncate>>toFloatinevery(2*second)|>liftfmain:SignalElementmain=constantforms|>lift2mapRotatetictac|>lift3collage(constant200)(constant200)-- equivalent with (<~) and (~) infix operatorsmain=letsignal1=mapRotate<~tictac~constantformsincollage<~constant200~constant200~signal1-- equivalent using ''constant'' to lift function typesmain=letsignal1=constantmapRotate~tictac~constantformsinconstantcollage~constant200~constant200~signal1
With color changing submit button.
importGraphics.InputasInputimportGraphics.ElementasElem-- button color modifier (pointfree)passwdOkColour:String->String->Element->ElementpasswdOkColourpasswd1passwd2=ifpasswd1==passwd2&&lengthpasswd1>=6thenElem.colorgreenelseElem.colorreddisplayfield1Elemfield2ElemsubmitButtonElem=field1Elem`above`field2Elem`above`submitButtonElemdynamicElement:SignalElementdynamicElement=let(field1ElemSignal,fld1StateSignal)=Input.password"Type password (min. 6 characters)!"labeledField1Signal=letprependLabel=beside(plainText"passwd: ")inliftprependLabelfield1ElemSignal(field2ElemSignal,fld2StateSignal)=Input.password"Retype password!"labeledField2Signal=letprependLabel=beside(plainText"control: ")inliftprependLabelfield2ElemSignal(submitButton,pressedSignal)=Input.button"Submit"coloredButSignal=constantsubmitButton|>lift3passwdOkColourfld1StateSignalfld2StateSignalinlift3displaylabeledField1SignallabeledField2SignalcoloredButSignalmain=dynamicElement
Note, as of Elm 0.10 Strings are no longer lists of characters, so the above becomes something more like this:
importGraphics.InputasInputimportGraphics.ElementasElemimportStringasS-- button color modifier (pointfree)passwdOkColour:String->String->Element->ElementpasswdOkColourpasswd1passwd2=ifS.lengthpasswd1>=6&&passwd1==passwd2thenElem.colorgreenelseElem.colorreddisplayfield1Elemfield2ElemsubmitButtonElem=field1Elem`above`field2Elem`above`submitButtonElemprependLabel=beside.plainTextdynamicElement:SignalElementdynamicElement=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=constantsubmitButton|>lift3passwdOkColourfld1StateSignalfld2StateSignalinlift3displaylabeledField1SignallabeledField2SignalcoloredButSignalmain=dynamicElement
|doi=10.1145/581690.581695 instead.