296 lines
9.2 KiB
Org Mode
296 lines
9.2 KiB
Org Mode
#+Title:Nicolas Meetings
|
|
|
|
* Etude Transient
|
|
** Installation et Test
|
|
*** Node.js
|
|
Il faut avoir une version récente de node.js (j'ai la 6.5.0)
|
|
*** Cabal file
|
|
#+BEGIN_SRC cabal
|
|
build-depends: base
|
|
, transient
|
|
, transient-universe
|
|
, transformers
|
|
, ghcjs-hplay
|
|
, containers
|
|
#+END_SRC
|
|
*** stack.yaml
|
|
#+BEGIN_SRC yaml
|
|
resolver: lts-6.15
|
|
packages:
|
|
- '.'
|
|
extra-deps:
|
|
- ghcjs-perch-0.3.3
|
|
- transient-0.4.2
|
|
- transient-universe-0.3.2.1
|
|
- ghcjs-hplay-0.3.4
|
|
flags: {}
|
|
extra-package-dbs: []
|
|
#+END_SRC
|
|
*** stack-ghcjs.yaml
|
|
#+BEGIN_SRC yaml
|
|
resolver: lts-6.15
|
|
packages:
|
|
- '.'
|
|
extra-deps:
|
|
- ghcjs-perch-0.3.3
|
|
- transient-0.4.2
|
|
- transient-universe-0.3.2.1
|
|
- ghcjs-hplay-0.3.4
|
|
flags: {}
|
|
extra-package-dbs: []
|
|
# GHCJS
|
|
compiler: ghcjs-0.2.0.9006015_ghc-7.10.3
|
|
compiler-check: match-exact
|
|
setup-info:
|
|
ghcjs:
|
|
source:
|
|
ghcjs-0.2.0.9006015_ghc-7.10.3:
|
|
url: "https://tolysz.org/ghcjs/lts-6.15-9006015.tar.gz"
|
|
sha1: 4d513006622bf428a3c983ca927837e3d14ab687
|
|
#+END_SR
|
|
*** Compile & Start
|
|
**** Compile
|
|
#+BEGIN_SRC zsh
|
|
#!/usr/bin/env bash -x
|
|
|
|
if command -v stack >/dev/null 2>&1
|
|
then echo "stack is already installed."
|
|
else
|
|
echo "stack is not installed. I will try to install it."
|
|
curl -sSL https://get.haskellstack.org/ | sh
|
|
fi
|
|
|
|
# Downloading Haskell Compiler this can be very long
|
|
stack setup
|
|
stack --stack-yaml stack-ghcjs.yaml setup
|
|
|
|
# Building the project the first time it will also download and compile
|
|
# library
|
|
stack build
|
|
stack --stack-yaml stack-ghcjs.yaml build
|
|
|
|
# Link GHCJS result to the correct directory (static/out.jsexe)
|
|
./mklink.sh
|
|
#+END_SRC
|
|
|
|
**** Start
|
|
|
|
#+BEGIN_SRC zsh
|
|
#!/usr/bin/env bash
|
|
|
|
stack exec -- wse-exe -p start/localhost/3000
|
|
#+END_SRC
|
|
|
|
**** Test
|
|
1. =./compile.sh=
|
|
2. =./start.sh=
|
|
3. Aller sur =http://localhost:3000= avec plusieurs navigateurs.
|
|
|
|
À noter des reload nécessaires avec Safari, marche très bien avec Firefox.
|
|
** Explication
|
|
*** Le Code
|
|
Voici le code suivant
|
|
|
|
#+BEGIN_SRC haskell
|
|
import Prelude hiding(div,id)
|
|
import Transient.Base
|
|
import Transient.Move
|
|
import Transient.EVars
|
|
import Transient.Move.Utils
|
|
import GHCJS.HPlay.View hiding (option,input)
|
|
import Data.IORef
|
|
import Data.String (fromString)
|
|
import Control.Monad.IO.Class
|
|
import Data.Monoid ((<>))
|
|
import Data.Typeable (Typeable)
|
|
|
|
data MyForm = MyForm { i1, i2 :: String } deriving (Show,Read,Typeable)
|
|
|
|
main :: IO ()
|
|
main = keep $ do
|
|
rdata <- liftIO $ newIORef (MyForm "i1" "i2")
|
|
dataAvailable <- newEVar
|
|
initNode $ formWidget rdata dataAvailable
|
|
<|> syncWidget dataAvailable
|
|
<|> longLivingProcess rdata dataAvailable
|
|
|
|
formWidget :: IORef MyForm -> EVar MyForm -> Cloud ()
|
|
formWidget rdata dataAvailable = onBrowser $ do
|
|
local . render . rawHtml $ do
|
|
h1 "State"
|
|
div ! id (fromString "msg") $ "No message yet."
|
|
st <- atRemote . localIO $ readIORef rdata -- read the state data
|
|
myForm <- local . render $ MyForm <$> getString (Just (i1 st)) `fire` OnKeyUp
|
|
<*> getString (Just (i2 st)) `fire` OnKeyUp
|
|
-- notify the long living process
|
|
atRemote $ localIO $ writeEVar dataAvailable myForm
|
|
|
|
syncWidget :: EVar MyForm -> Cloud ()
|
|
syncWidget dataAvailable = onBrowser $ do
|
|
-- display in the browser
|
|
received <- atRemote $ local $ readEVar dataAvailable
|
|
local . render . at (fromString "#msg") Insert . rawHtml $
|
|
p $ "Check other navigators: " <> i1 received <> ", " <> i2 received
|
|
|
|
longLivingProcess :: IORef MyForm -> EVar MyForm -> Cloud ()
|
|
longLivingProcess rdata dataAvailable =
|
|
onServer $ local $ do
|
|
liftIO $ print "longliving"
|
|
dat <- readEVar dataAvailable
|
|
liftIO $ writeIORef rdata dat
|
|
liftIO $ print $ show dat <> " arrived"
|
|
#+END_SRC
|
|
|
|
Que se passe-t-til ?
|
|
|
|
On voit un formulaire de deux input. Lorsque l'on modifie un champs, un message
|
|
se met à jour automatiquement et on voit que le formulaire est envoyé au serveur
|
|
lorsque l'on regarde les logs.
|
|
On remarque que tous les messages se synchronisent sur tous les navigateurs!
|
|
|
|
*** Les widgets
|
|
|
|
- formWidget (le formulaire)
|
|
- longLivingProcess (le process sur le serveur)
|
|
- syncWidget (le message au dessus du formulaire)
|
|
|
|
Lorsque l'on utilise `(<|>)` celà signifie que l'on va lancer le code sur toutes
|
|
les instances de l'application (toutes les instances frontend et backend lancées).
|
|
|
|
Mais on peut lancer des commandes seulement sur le frontend, d'autres seulement
|
|
en backend.
|
|
|
|
**** Init
|
|
- on initialise un IORef qui joue le rôle d'une DB.
|
|
- Dans une vrai application il faudrait remplacer IORef par un système
|
|
persistant
|
|
- On initialise aussi un EVar (Event Var) donc on va envoyer des events.
|
|
**** formWidget (le formulaire)
|
|
|
|
#+BEGIN_SRC haskell
|
|
formWidget :: IORef MyForm -> EVar MyForm -> Cloud ()
|
|
formWidget rdata dataAvailable = onBrowser $ do
|
|
local . render . rawHtml $ do
|
|
h1 "State"
|
|
div ! id (fromString "msg") $ "No message yet."
|
|
st <- atRemote . localIO $ readIORef rdata -- read the state data
|
|
myForm <- local . render $ MyForm <$> getString (Just (i1 st)) `fire` OnKeyUp
|
|
<*> getString (Just (i2 st)) `fire` OnKeyUp
|
|
-- notify the long living process
|
|
atRemote $ localIO $ writeEVar dataAvailable myForm
|
|
#+END_SRC
|
|
|
|
- le `onBrowser` au début montre bien que on ne s'exécute que sur le browser.
|
|
- le `local` signifie que l'on fait quelque chose en local sur le front
|
|
- on a un `atRemote` qui signifie que cette opération doit avoir lieu sur une
|
|
instance `remote` (le serveur)
|
|
- en remote on récupère simplement la valeur dans le IORef et on la récupère
|
|
coté front (black magic!)
|
|
- les `fire` OnKeyUp signifie que la continuation (ce qu'il y a après la ligne
|
|
courante) et relancée à chaque event keyUp.
|
|
|
|
**** longLivingProcess (le process sur le serveur)
|
|
|
|
#+BEGIN_SRC haskell
|
|
longLivingProcess :: IORef MyForm -> EVar MyForm -> Cloud ()
|
|
longLivingProcess rdata dataAvailable =
|
|
onServer $ local $ do
|
|
liftIO $ print "longliving"
|
|
dat <- readEVar dataAvailable
|
|
liftIO $ writeIORef rdata dat
|
|
liftIO $ print $ show dat <> " arrived"
|
|
#+END_SRC
|
|
|
|
- le `onServer` signifie bien que l'on est sur le serveur
|
|
- est le code est très simple, lorsqu'il reçoit un évênement via l'EVar, il le
|
|
sauvegarde dans l'IORef et il log la valeur reçue
|
|
|
|
**** syncWidget (le message sous le formulaire)
|
|
|
|
#+BEGIN_SRC haskell
|
|
syncWidget :: EVar MyForm -> Cloud ()
|
|
syncWidget dataAvailable = onBrowser $ do
|
|
-- display in the browser
|
|
received <- atRemote $ local $ readEVar dataAvailable
|
|
local . render . at (fromString "#msg") Insert . rawHtml $
|
|
p $ "Check other navigators: " <> i1 received <> ", " <> i2 received
|
|
#+END_SRC
|
|
|
|
- le onBrowser est là pour signifier que l'on se situe sur le browser
|
|
- on "écoute" l'EVar à partir du serveur
|
|
- dans le render on écrase (via un insert JS) par du HTML maison.
|
|
|
|
L'idée du on écoute, c'est simplement que chaque ligne du `do` sauvegarde des
|
|
continuations qui se relancent lorsque l'on fait un readEVar par exemple.
|
|
|
|
*** Retour
|
|
|
|
Donc après avoir joué avec cette première étape et essayer de comprendre comment
|
|
utiliser la librairie. Je dois dire que je suis plutôt impressioné. J'avais peur
|
|
de tomber sur du code "magique". Mais ça n'est pas tant le cas.
|
|
|
|
Ce qui a l'air magique :
|
|
|
|
- décrire au même endroit comment doit se comporter le front et le back et
|
|
comment ils interagissent.
|
|
- Communication entre process qu'ils soient dans un navigateur web ou pas
|
|
|
|
La monade transient est certes complexe, mais la première étape est de
|
|
comprendre que la monade capture les continuations.
|
|
|
|
En ce qui concerne l'utilsation avancé du typage. Je ne pense pas qu'il en fasse
|
|
grand usage. Il utilise juste ce qui est nécessaire pour ne pas faire bugger sa
|
|
lib. Par contre il n'est jamais demandé à l'utiliser de créer des types.
|
|
|
|
De mon point de vue, sa monade reste très (trop?) ouverte.
|
|
|
|
J'ai aussi constaté des bugs assez incompréhensibles, suffisamment pour avoir un
|
|
peu peur d'utiliser ça en prod.
|
|
À mon avis nous avons deux choix pour avancer:
|
|
|
|
- soit modifier sa lib pour utiliser =Transient m= plutôt que =TransIO =
|
|
Transient IO= directement.
|
|
- Soit nous créer une Free Monade qui copie l'API de transient pour nos besoins
|
|
et dont l'un des interpréteur appelera transient.
|
|
|
|
On peut aussi imaginer un quelque chose entre les deux. Pour ma part, je vais
|
|
discuter un peu avec Corona en ce qui concerne les bugs, le fait qu'il travaille
|
|
sous windows ne doit pas aider je présume.
|
|
|
|
* 2016-09-05
|
|
- Transient, MFlow
|
|
- Kmett: Monad Homomorphisms
|
|
* 2016-07-29
|
|
** Attribute Grammar
|
|
- https://wiki.haskell.org/The_Monad.Reader/Issue4/Why_Attribute_Grammars_Matter
|
|
- AST → AST
|
|
- Repmin: un seul parcours d'arbre
|
|
- Transducer de DAG
|
|
- Patrick Bahr
|
|
- Composing & Decomposing Data Types (Patrick Bahr)
|
|
** x TODO
|
|
- pl-cli demo (avec la liste des constructors)
|
|
- pl-server
|
|
* 2016-07-11
|
|
** List value constructors from a types
|
|
|
|
~~~~
|
|
ghci> :m +Data.Data
|
|
ghci> let x = toConstr (Just False)
|
|
ghci> constrType x
|
|
DataType {tycon = "Prelude.Maybe", datarep = AlgRep [Nothing,Just]}
|
|
~~~~
|
|
|
|
~~~
|
|
Prelude Data.Data> :set -XDeriveDataTypeable
|
|
Prelude Data.Data> data Cmd = X | Foo Int | Bar Bool deriving (Data)
|
|
Prelude Data.Data> let y = toConstr X
|
|
Prelude Data.Data> constrType y
|
|
DataType {tycon = "Cmd", datarep = AlgRep [X,Foo,Bar]}
|
|
~~~
|
|
|
|
import ... (Cmd(..))
|
|
|
|
- Cmd ????
|
|
- Cmd => [X,Foo,Bar] ????
|
|
- Foo => Foo :: Int -> Cmd ????
|