Elm (llenguatge de programació)

De Viquipèdia
Dreceres ràpides: navegació, cerca

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.

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ó[modifica | modifica el codi]

La implementació de 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[modifica | modifica el codi]

Elm 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[modifica | modifica el codi]

Els valors són variables o expressions immutables. Entre els seus tipus tenim:

Senyals (variabilitat)[modifica | modifica el codi]

(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[modifica | modifica el codi]

Els 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.

Des de la perspectiva del llenguatge Haskell[modifica | modifica el codi]

Senyal com a instància de la classe Functor[modifica | modifica el codi]

Vegeu signatura de la classe Functor a Haskell.[14] De la biblioteca Signal de 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[modifica | modifica el codi]

Vegeu signatura de la classe Applicative a Haskell.[15] De la biblioteca Signal de 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 | modifica el codi]

És un tipus quina finalitat és possibilitar definir una seqüència de transformacions en cadena (amb possibles efectes coŀ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 de 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 | modifica el codi]

  • 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 | modifica el codi]

mkdir elm-compiler && cd elm-compiler
cabal-dev install elm elm-server
 
export PATH=$PWD/cabal-dev/bin:$PATH

Exemples de Valors (sense variabilitat)[modifica | modifica el codi]

Text estilitzat[modifica | modifica el codi]

import 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[modifica | modifica el codi]

Vegeu 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[modifica | modifica el codi]

La construcció 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)[modifica | modifica el codi]

Gràfics que varien al Tictac[modifica | modifica el codi]

Fent 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[modifica | modifica el codi]

import 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é[modifica | modifica el codi]

Referències[modifica | modifica el codi]

  1. Operadors d'aplicació
  2. 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.
  3. Elm - What is Functional Reactive Programming?
  4. 4,0 4,1 Elm - The Libraries You Need Automatons
  5. 5,0 5,1 5,2 Biblioteca Prelude
  6. Char library
  7. Text library
  8. Date library
  9. Element library
  10. Collage library
  11. 11,0 11,1 Registres extensibles
  12. Sintaxi de Elm - Tipus de dades algebraics
  13. Graphics.Input library
  14. Haskell class Functor def.
  15. Haskell class Applicative def.
  16. Biblioteca Automaton
  17. Haskell Arrows introduction
  18. Biblioteques
  19. JavaScript FFI

Enllaços externs[modifica | modifica el codi]