Elm (llenguatge de programació)
Elm és un llenguatge de programació funcional i tipatge fort per crear interfícies d'usuari basades en navegadors web, generant estructures dinàmiques basades en JavaScript.
Tipus | functional reactive programming language (en) , llenguatge de programació funcional, reactive programming language (en) , llenguatge de programació, llenguatge de programació purament funcional i llenguatge de regles fora de joc |
---|---|
Data de creació | 2012 |
Disseny | Evan Czaplicki (en) |
Paradigma de programació | programació funcional, programació reactiva, purely functional programming (en) i functional reactive programming (en) |
Darrera versió estable | 0.19.1 () |
Llenguatge de programació | Haskell |
Influenciat per | Haskell, ML Estàndard, OCaml i F Sostingut |
Extensió dels fitxers | elm |
Codi font | Codi font |
Llicència | llicència BSD |
Etiqueta d'Stack Exchange | Etiqueta |
Pàgina web | elm-lang.org |
Elm utilitza la programació reactiva funcional i un sistema de gràfics funcional pur per construir interfícies d'usuari sense actualitzacions destructives.
Utilitza una sintaxi molt similar al Haskell però amb diferent semàntica. Elm és d'avaluació estricta (l'avaluació va de l'arrel a les branques començant per la funció main) i primerenca (en un àmbit local les instruccions s'executen per ordre d'aparició; no hi ha clàusules where).
Descripció
modificaLa implementació d'Elm compila a JavaScript i HTML, amb ús d'estils CSS.
A Elm, la programació reactiva funcional, basada en objectes variables i relacions de dependència, relleva l'usuari de programar gestors d'esdeveniments (ang: event handlers) i rutines en mode d'inversió de control (ang:callbacks). També actualitza automàticament els objectes en pantalla.
La presentació gràfica funcional pura relleva d'actualitzar el model de document DOM.
Elm també permet incloure plantilles en llenguatge de marques Markdown.
Sintaxi i semàntica
modificaElm adopta una sintaxi a l'estil del Haskell amb influències d'OCaml i F Sostingut. Per exemple l'operador "té tipus" s'escriu amb un únic caràcter ':'; els operadors d'aplicació directa (<|) i aplicació revessa (|>) són tals que (f x) és igual que (f |> x) i igual que (x |> f).[1]
Les definicions de tipus segueixen l'esquema del Haskell. Els tipus dels paràmetres se separen amb (->) i la repetició de variables de tipus indica requeriment de coincidència.
Elm té registres extensibles que forneixen un ús segur de la flexibilitat del model d'objectes de Javascript.
El sistema de tipus suporta tipus primitius com Enters i Coma flotant, dades estructurades com ara tuples i registres, i també tipus abstractes de dades personalitzats.
La implementació de la programació reactiva funcional és moguda per esdeveniments (event-driven), volent dir que les actualitzacions són automàtiques i només en cas de necessitat. Comparteix la màxima semblança amb l'"Event-driven FRP"[2][3] i l'"Arrowized FRP".[4]
El següent programa mostra la posició del ratolí en moure'l per la pantalla.
main = lift asText Mouse.position
El valor de main es mostra en pantalla. La funció asText converteix qualsevol valor en un valor textual representable. La funció lift assegura que asText serà aplicada al senyal Mouse.position a cada canvi del valor.
Elm té un petit però expressiu conjunt de construccions de llenguatge i tractament de llistes. També té un sistema de mòduls i un model d'interfase amb funcions foranes (FFI) per al JavaScript.
Les funcions predefinides són al mòdul Prelude.[5]
Valors
modificaEls valors són variables o expressions immutables. Entre els seus tipus tenim:
- Bool, number, Int, Float.[5]
- Char,[6] String, Text (incorpora estil)[7]
- Time, Date.[8]
- Element (elements estructurals)[9]
- Form (representació gràfica), Shape, Path.[10]
- Registres anònims extensibles (tractament funcional dels objectes de JavaScript)[11]
- Tipus de dades algebraics (com al Haskell; facilita l'encaix de patrons).[12]
Senyals (variabilitat)
modifica(Signal tipusDelValor) és el tipus de les variables o expressions que canvien de valor, amb el temps (continus) o per l'efecte d'esdeveniments (discrets).
-- obtenir el temps actual, actualitzat cada t
Time.every : Time -> Signal Time
-- obtenir la posició del ratolí
Mouse.position : Signal (Int,Int)
Els elements de formularis HTML poden variar en estil i en estat. Les funcions que els generen tornen majoritàriement un parell de senyals (senyal_de_l'element, senyal_de_l'estat)[13] com a
-- crear una casella de marcar, amb el valor inicial indicat.
-- retorna el parell (senyal de l'estructura, senyal de l'estat)
checkbox : Bool -> (Signal Element, Signal Bool)
Senyals dependents
modificaEls senyals dependents són com les cel·les d'un full de càlcul que contenen una fórmula, i el seu valor es recalcula per reacció als canvis dels seus operands.
Es defineixen, o bé amb una expressió de filtre de senyals, o bé per l'aplicació d'una funció de valors, que per aplicar-la a senyals cal elevar-ne el tipus amb lift.
Per emprar un valor en un paràmetre de senyal, cal elevar-ne el tipus convertint-lo a senyal amb la funció Signal.constant.
Si la funció arrel (d'engegada), anomenada main, és un senyal, llavors s'exploraran les dependències entre senyals per obtenir-ne els independents i incloure'n els generadors en el bucle de despatx inicial.
Cada senyal manté una llista dels senyals que en depenen i que farà recalcular en cas que se li actualitzi el valor. No hi pot haver cicles en la relació de dependència.
Senyal com a instància de la classe Functor
modificaVegeu signatura de la classe Functor a Haskell.[14] De la biblioteca Signal d'Elm:
-- elevar una funció de ''valors'' a una funció de ''senyals''
lift : (a -> b) -> Signal a -> Signal b
-- (<~) és un àlies per a lift
Senyal com a instància de la classe Applicative
modificaVegeu signatura de la classe Applicative a Haskell.[15] De la biblioteca Signal d'Elm:
-- elevar un valor
constant : a -> Signal a
-- aplicació seqüencial de funció de senyals
(~) : Signal (a -> b) -> Signal a -> Signal b
-- mostreja el segon senyal seqüencialment a un canvi de valor del primer.
sampleOn : Signal a -> Signal b -> Signal b
----------------------
-- per elevar una funció de valors de N paràmetres (definides lift2 a lift8)
senyal_resultant = liftN fun_N_ària senyal1 senyal2 ... senyalN
-- equivalent amb operadors binaris en infix
senyal_resultant = fun_N_ària <~ senyal1 ~ senyal2 ~ ... ~ senyalN
Transformadors de senyals componibles. Autòmats
modificaÉs un tipus quina finalitat és possibilitar definir una seqüència de transformacions en cadena (amb possibles efectes col·laterals).
El concepte és originari del TAD Fletxa de Haskell que descriu la seqüenciació d'efectes col·laterals mitjançant l'encadenament de morfismes.
La baula d'aquest encadenament és el tipus Automaton, que designa una aplicació de valors (amb possibles efectes col·laterals) amb un paràmetre d'origen i un resultat. El tipus és polimòrfic amb dos paràmetres, el tipus de l'entrada i el de la sortida (Automaton input output).
Per encadenar-ne dues, el tipus de la sortida de l'autòmata precedent ha de coincidir amb el tipus de l'entrada del següent.
Podem generar-ne a partir d'una funció pura (que només depèn dels paràmetres) o d'una amb memòria. De la biblioteca Automaton d'Elm:[16]
-- generador des d'una funció pura
pure : (a -> b) -> Automaton a b
-- generadors a partir d'un estat inicial i una funció de l'entrada i de l'estat
state : b -> (a -> b -> b) -> Automaton a b
hiddenState : s -> (a -> s -> (s,b)) -> Automaton a b
En podem fer un transformador de senyals amb la funció Automaton.run i un valor per defecte.
run : Automaton a b -> b -> Signal a -> Signal b
-- cal aportar un resultat per defecte, per al cas de manca de senyal d'entrada
senyalResultant = run elMeuAutòmata resultatPerDefecte senyalD'entrada
Els Automatons són componibles:
-- Compon dos autòmats mitjançant l'encadenament
(>>>) : Automaton a b -> Automaton b c -> Automaton a c
...
En realitat aquests autòmats componibles són una versió particular del concepte de les Fletxes de Haskell.[17][4]
Contenidors
modifica- List, Set, Dict.[18]
- Maybe (paràmetres opcionals i resultats de funcions definides parcialment, com a Nothing, o bé Just v)
- Either (resultats de funcions definides parcialment amb informació de l'error, com a Right resultat o bé Left error)
Per processar una llista de senyals en un mateix paràmetre:
-- combina una llista de senyals en un senyal de la llista de valors
Signal.combine : [Signal a] -> Signal [a]
-- actualitza amb la primera que varia (cas de simultaneitat, la primera de la llista)
merges : [Signal a] -> Signal a
Eines
modifica- Editor i compilador en-línia: elm-lang.org/try
- Aprendre amb exemples Arxivat 2016-03-13 a Wayback Machine.
- Biblioteques Elm Arxivat 2013-08-11 a Wayback Machine.
- Funcions predefinides.[5]
- Instal·lació en UNIX mitjançant l'entorn Haskell amb l'instal·lador cabal-dev
mkdir elm-compiler && cd elm-compiler
cabal-dev install elm elm-server
export PATH=$PWD/cabal-dev/bin:$PATH
Exemples de Valors (sense variabilitat)
modificaText estilitzat
modificaimport Graphics.Element as Elem -- importació qualificada
unstyledText : Text
unstyledText = Text.toText "test1"
styleIt : Text -> Text
styleIt = (Text.typeface "serif"). (Text.color red)
-- (f <| x = f x), evita l'ús de parèntesis
alignTest : Int -> Element
alignTest commonWidth =
let elem1 = Elem.width commonWidth <| Text.text <| styleIt unstyledText
elem2 = Elem.width commonWidth <| Text.centered <| styleIt <| Text.toText "test2"
elem3 = Elem.width commonWidth <| Text.righted <| styleIt <| Text.toText "test3"
in flow down [elem1, elem2, elem3]
main : Element
main = alignTest 200
Podeu provar-ho a l'editor online amb compilació i execució.
Polimorfisme en tipus registres
modificaVegeu registres.[11]
module MyModule where
import Color
type Named a = { a | name : String } -- registres que tinguin el camp ''name''
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
- incrustant-lo en un element div
<!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> <!-- cal que el contenidor sigui un DIV i que estigui buit -->
<script type="text/javascript">
Elm.embed(Elm.MyModule, document.getElementById('myId'))
</script>
<noscript>Javascript is not enabled</noscript>
</body></html>
- compilació i prova offline
# compila a Javascript
$ elm --make -s --only-js MyModule.elm
# executa
$ browser MyModule.html
Parametritzant un programa Elm
modificaLa clàusula port per a la interfície FFI (Foreign function interface) amb Javascript[19] es pot aprofitar per proporcionar paràmetres a nivell de html.
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 i els mòduls compilats js
(si es compila amb --make el ElmMain.js contindrà els mòduls d'usuari que s'importin -->
<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", // de la lletra de la cançó "abc" dels "Jackson's 5"
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>
Exemples de Senyals (amb variabilitat)
modificaGràfics que varien al Tictac
modificaFent servir la biblioteca Graphics.Collage
myShape : Shape
myShape1 = circle 30
myShape2 = rect 60 60
myForm1 : Form
myForm1 = outlined (dashed green) myShape1
myForm2 = outlined (solid red) myShape2
formes : [Form]
formes = [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
f >> g = g. f
-- senyal de temps en segons, sense decimals
tictac : Signal Float
tictac = let f = inSeconds >> truncate >> Prelude.toFloat
in every (2 * second)
|> lift f
main : Signal Element
main = constant formes
|> lift2 mapRotate tictac
|> lift3 collage (constant 200) (constant 200)
Comprovador de contrasenya en camp doblat, amb confirmació per canvi de color
modificaimport Graphics.Input as Input
import Graphics.Element as Elem
import String as Str
-- comprovador amb modificador de color
passwdOkColour : String -> String -> Element -> Element
passwdOkColour passwd1 passwd2 =
if passwd1 == passwd2 && Str.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
Vegeu també
modificaReferències
modifica- ↑ «Operadors d'aplicació». Arxivat de l'original el 2013-06-07. [Consulta: 25 juliol 2013].
- ↑ Wan, Zhanyong; Taha, Walid; Hudak, Paul «Event-Driven FRP». Proceedings of the 4th International Symposium on Practical Aspects of Declarative Languages, 2002, pàg. 155–172.
- ↑ «Elm - What is Functional Reactive Programming?». Arxivat de l'original el 2018-03-23. [Consulta: 30 juliol 2013].
- ↑ 4,0 4,1 Elm - The Libraries You Need Arxivat 2016-03-04 a Wayback Machine. Automatons
- ↑ 5,0 5,1 5,2 «Biblioteca Prelude». Arxivat de l'original el 2013-09-05. [Consulta: 30 juliol 2013].
- ↑ «Char library». Arxivat de l'original el 2016-03-04. [Consulta: 30 juliol 2013].
- ↑ «Text library». Arxivat de l'original el 2013-08-17. [Consulta: 30 juliol 2013].
- ↑ «Date library». Arxivat de l'original el 2012-12-14. [Consulta: 30 juliol 2013].
- ↑ «Element library». Arxivat de l'original el 2013-09-05. [Consulta: 30 juliol 2013].
- ↑ «Collage library». Arxivat de l'original el 2013-08-17. [Consulta: 30 juliol 2013].
- ↑ 11,0 11,1 «Registres extensibles». Arxivat de l'original el 2013-04-15. [Consulta: 30 juliol 2013].
- ↑ «Sintaxi de Elm - Tipus de dades algebraics». Arxivat de l'original el 2016-03-13. [Consulta: 30 juliol 2013].
- ↑ «Graphics.Input library». Arxivat de l'original el 2013-08-17. [Consulta: 30 juliol 2013].
- ↑ «Haskell class Functor def.». Arxivat de l'original el 2013-06-09. [Consulta: 30 juliol 2013].
- ↑ «Haskell class Applicative def.». Arxivat de l'original el 2013-12-03. [Consulta: 30 juliol 2013].
- ↑ «Biblioteca Automaton». Arxivat de l'original el 2013-06-13. [Consulta: 30 juliol 2013].
- ↑ Haskell Arrows introduction
- ↑ «Biblioteques». Arxivat de l'original el 2013-08-11. [Consulta: 30 juliol 2013].
- ↑ «JavaScript FFI». Arxivat de l'original el 2016-03-13. [Consulta: 30 juliol 2013].
Enllaços externs
modifica