deft/archives/Nicolas.org
Yann Esposito (Yogsototh) 5138e54776
updated files
2019-04-24 21:27:19 +02:00

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 ????