380 lines
14 KiB
Org Mode
380 lines
14 KiB
Org Mode
#+Title: Alexandre Delanöe
|
|
#+Author: Yann Esposito
|
|
|
|
* <2018-09-05 Wed>
|
|
- John Rolls théorie de la justice. Se concentrer sur le plus mal loti.
|
|
- democracie liquide
|
|
-
|
|
* <2018-07-09 Mar>
|
|
|
|
** Purescript thermite vs halogen
|
|
|
|
#+BEGIN_SRC purescript
|
|
data Component state action eff props =
|
|
Component { initialState :: State
|
|
, update :: PerformAction eff State props Action -- performAction
|
|
, view :: Spec eff State props Action -- spec
|
|
}
|
|
#+END_SRC
|
|
* <2018-07-10 Mar>
|
|
|
|
** Notes de pre-lecture du code de =purescript-gargantext=
|
|
|
|
*** Main
|
|
|
|
Utilise Navigation pour avoir le =layoutSpec= (la vue) et =initAppState=.
|
|
Utilise aussi =unsafePartial=.
|
|
|
|
Effects, seulement, dom, console et ajax.
|
|
|
|
détails:
|
|
J'aurai ajouté un type à =setRouting= pour signifier qu'il s'agit d'une fn qui
|
|
va appliquer une action à un routing.
|
|
|
|
le unsafePartial ne semble pas tellement utile, mais pas dangereux non plus.
|
|
|
|
*** Navigation
|
|
|
|
Semble centraliser toutes les pages du site.
|
|
Elle centralise l'état de l'application qui est un produit de tous les états
|
|
des sous-composants.
|
|
|
|
C'est une première grosse différence avec Halogen il me semble. Dans halogen,
|
|
chaque composant à son état initial. Ici, c'est au composant =root= de s'occuper
|
|
de l'initialisation comme avec elm.
|
|
|
|
#+BEGIN_SRC purescript
|
|
initAppState :: AppState
|
|
initAppState =
|
|
{ currentRoute : Just Home
|
|
, landingState : L.initialState
|
|
, loginState : LN.initialState
|
|
, addCorpusState : AC.initialState
|
|
, docViewState : DV.tdata
|
|
, searchState : S.initialState
|
|
, userPage : U.initialState
|
|
, docAnnotationView : D.initialState
|
|
, ntreeView : NT.exampleTree
|
|
, tabview : TV.initialState
|
|
, search : ""
|
|
, corpusAnalysis : CA.initialState
|
|
, showLogin : false
|
|
, showCorpus : false
|
|
, graphExplorer : GE.initialState
|
|
, initialized : false
|
|
, ngState : NG.initialState
|
|
, dashboard : Dsh.initialState
|
|
}
|
|
#+END_SRC
|
|
|
|
La composition des action se fait de la même manière. Avec un type produit de
|
|
toutes les actions des sous-composants:
|
|
|
|
#+BEGIN_SRC purescript
|
|
data Action
|
|
= Initialize
|
|
| LandingA L.Action
|
|
| LoginA LN.Action
|
|
| SetRoute Routes
|
|
| AddCorpusA AC.Action
|
|
| DocViewA DV.Action
|
|
| SearchA S.Action
|
|
| UserPageA U.Action
|
|
| DocAnnotationViewA D.Action
|
|
| TreeViewA NT.Action
|
|
| TabViewA TV.Action
|
|
| GraphExplorerA GE.Action
|
|
| DashboardA Dsh.Action
|
|
| Search String
|
|
| Go
|
|
| CorpusAnalysisA CA.Action
|
|
| ShowLogin
|
|
| ShowAddcorpus
|
|
| NgramsA NG.Action
|
|
|
|
#+END_SRC
|
|
|
|
Contrairement à halogen, il n'y a pas de
|
|
distinction entre les actions interne aux sous-composants et aux messages envoyé
|
|
du sous-composant au composant père. Il me semble cependant qu'il n'y a pas
|
|
besoin de déclarer des actions des sous-sous-composants.
|
|
|
|
En tout cas, ici, la situation semble plus avancée et peut-être plus saine avec
|
|
Halogen. Cependant je vois difficilement comment "simuler" la situation halogen
|
|
avec thermite.
|
|
|
|
Celà étant dit, la complexité du composant s'en ressent, et aussi le "couplage"
|
|
entre composant fils et père est donc plus importante avec thermite qu'avec Halogen.
|
|
Cependant, tant que l'application frontend ne dépasse pas une complexité énorme.
|
|
Je ne pense pas que celà constitue un réel problème avant longtemps.
|
|
|
|
La situation est déjà beaucoup plus clean que dans la plupart des autres
|
|
systèmes qui font du frontend. On est au moins au niveau de clarté
|
|
d'organisation de elm ici.
|
|
|
|
|
|
**** Digression sur la distinction des Action en Queries et Events
|
|
Une petite remarque il faudrait vérifier si possible que le nomage des actions
|
|
corresponde dans la mesure du possible à du vocabulaire du Domaine. Un peu comme
|
|
avec le DDD. C'est-à-dire que dans l'idéal les actions devrait être
|
|
compréhensible par une personne non technique. Typiquement "bouton-nouveau-tab-cliqué" etc...
|
|
|
|
Il me semble aussi qu'il faut bien faire des distinctions entre action passées
|
|
(ButtonClicked qui sous-entendent que l'action a été acceptée et devrait
|
|
systématiquement provoquer un changement d'état) des "Queries" qui sont des
|
|
action de demande de quelque chose par exemple "GetCorpora" qui n'est donc pas
|
|
au passé mais au présent et qui n'engage aucun changement d'état mais va
|
|
provoquer un effet de bord qui lui engendrera potentiellement un ou plusieurs
|
|
Event qui eux provoqueront un changement d'état.
|
|
|
|
Ce n'est pas essentiel, mais je trouve que c'est une bonne distinction à avoir
|
|
pour clarifier la situation. En particulier cela perme de splitter la fn
|
|
=performAction= en deux sous-parties. Une partie "pure" qui s'occupe des actions
|
|
passée et qui ne provoque pas d'effet de bord (ou à la limite juste console).
|
|
Et une partie impure qui n'a pas accès à la state monade pour changer l'état interne.
|
|
Mais qui doit pouvoir re-evenvoyer des events.
|
|
|
|
Il me semble qu'à ce stade il est un peu prématuré de s'occuper de ça. Ce n'est
|
|
pas urgent. Malgré tout, celà permettrait de faire encore un peu plus propre. En
|
|
particulier, celà peut-être très utile pour debugger. Parce qu'il suffit de
|
|
s'occupter des Actions de type Event et d'oublier les actions de type Query pour
|
|
reproduire l'état interne. Cela permet entre autre de "sauvegarder" les résulats
|
|
des effets de bords qu'un utilisateur aurait rencontré. Et ainsi en regardant
|
|
les Event on peut retrouver à la fois l'etat interne de l'app à un moment donné
|
|
pour un utilisateur avec aussi le moyen de remonter le temps pour voir à quel
|
|
moment un bug s'est produit pour un utilisateur dans ses conditions à lui (par
|
|
exemple pb de connection difficile à reproduire)
|
|
|
|
Pour en revenir au cas particulier de l'app. On voit par exemple l'action
|
|
=ShowLogin=, qui n'a pas d'effet de bord (à part du log) et je renommerai cette
|
|
action en un event qui devrait être un verbe au passé. =LoginPageSelected= ou
|
|
quelque chose de similaire.
|
|
|
|
Comme je l'ai dit c'est prématuré, mais peut-être penser à cette disctinction
|
|
sera utile pour minimiser un tout petit peu la complixité de l'appli et gagner
|
|
en "scalabilité de l'app". Par scalabilité j'entends la capacité d'ajouter
|
|
facilement de nouveaux composant en minimisant le coût de dev.
|
|
|
|
*Remarque importante*: je n'ai pas fait cette distinction dans mon code avec
|
|
Halogen parce je n'en ai pas encore ressenti la nécessité.
|
|
|
|
*** Lenses
|
|
|
|
J'imagine qu'il n'était pas possible d'avoir une fonction qui génère les lenses
|
|
automatiquement comme c'est le cas avec =generic-lenses= en Haskell.
|
|
Celà rend malheureusmeent la lecture des modules un peu pénible à cause de la grande
|
|
répétition du code.
|
|
|
|
**** Digression sur l'isolation et l'organisation
|
|
|
|
Personnellement j'aurai peut-être isolé ces déclarations soit en fin de fichier
|
|
après un commentaire qui marque bien la separation
|
|
|
|
#+BEGIN_SRC purescript
|
|
-- ========== Lenses Declarations =============
|
|
#+END_SRC
|
|
|
|
L'autre possibliité étant de créer un répertoire différent par composant. Chaque
|
|
répertoire contenant les parties qui le compose, notamment imaginer un module
|
|
Lenses par composant:
|
|
|
|
#+BEGIN_SRC
|
|
Component/
|
|
Actions.purs
|
|
View.purs
|
|
Lenses.purs
|
|
#+END_SRC
|
|
|
|
Ce qu'il ne faut surtout pas faire, c'est de créer un répertoire qui mixe
|
|
les infos de tous les composants (c'est pourtant courant).
|
|
Typiquement il faut éviter
|
|
|
|
#+BEGIN_SRC
|
|
!! DON'T DO THAT
|
|
Actions/
|
|
Component1.purs
|
|
Component2.purs
|
|
...
|
|
ComponentN.purs
|
|
Lenses/
|
|
Component1.purs
|
|
Component2.purs
|
|
...
|
|
ComponentN.purs
|
|
Views/
|
|
Component1.purs
|
|
Component2.purs
|
|
...
|
|
ComponentN.purs
|
|
#+END_SRC
|
|
|
|
Vraiment c'est tout pourri de faire, ça. C'est angular qui fait ça et ça rend la
|
|
vie des developper nulle. Mais ça ne se voit pas tout de suite. Donc vraiment il
|
|
faut regrouper les composants, pas séparer les concerns techniques. De plus
|
|
cette façon de découper est anti-modulaire.
|
|
|
|
*** Views (Spec)
|
|
|
|
L'idée d'utiliser des lenses pour les etats et les actions est très bonne en tout cas.
|
|
Ça me semble la chose naturelle à faire.
|
|
Il me semble qu'avec Halogen, ce genre de chose se fait plus naturellement.
|
|
Typiquement voici le code pour sélectionner le composant visibile en fonction de la route:
|
|
|
|
#+BEGIN_SRC purescript
|
|
pagesComponent :: forall props eff. AppState -> Spec (E eff) AppState props Action
|
|
pagesComponent s =
|
|
case s.currentRoute of
|
|
Just route ->
|
|
selectSpec route
|
|
Nothing ->
|
|
selectSpec Home
|
|
where
|
|
selectSpec :: Routes -> Spec ( ajax :: AJAX
|
|
, console :: CONSOLE
|
|
, dom :: DOM
|
|
| eff
|
|
) AppState props Action
|
|
selectSpec CorpusAnalysis = layout0 $ focus _corpusState _corpusAction CA.spec'
|
|
selectSpec Login = focus _loginState _loginAction LN.renderSpec
|
|
selectSpec Home = layout0 $ focus _landingState _landingAction (L.layoutLanding EN)
|
|
selectSpec AddCorpus = layout0 $ focus _addCorpusState _addCorpusAction AC.layoutAddcorpus
|
|
selectSpec DocView = layout0 $ focus _docViewState _docViewAction DV.layoutDocview
|
|
selectSpec UserPage = layout0 $ focus _userPageState _userPageAction U.layoutUser
|
|
selectSpec (DocAnnotation i) = layout0 $ focus _docAnnotationViewState _docAnnotationViewAction D.docview
|
|
selectSpec Tabview = layout0 $ focus _tabviewState _tabviewAction TV.tab1
|
|
-- To be removed
|
|
selectSpec SearchView = layout0 $ focus _searchState _searchAction S.searchSpec
|
|
selectSpec NGramsTable = layout0 $ focus _ngState _ngAction NG.ngramsTableSpec
|
|
selectSpec PGraphExplorer = focus _graphExplorerState _graphExplorerAction GE.specOld
|
|
selectSpec Dashboard = layout0 $ focus _dashBoardSate _dashBoardAction Dsh.layoutDashboard
|
|
#+END_SRC
|
|
|
|
Ça rend le code correct. Mais on sent qu'il y a encore pas mal de boilerplate.
|
|
Honnêtement je trouve ça très acceptable.
|
|
Malgré tout je préfère encore la situation avec Halogen.
|
|
|
|
Je vais tenter d'expliquer pourquoi.
|
|
|
|
Avec halogen, le rendu se fait avec une fonction qui s'appelle ~slot~ ou la
|
|
fonction plus générique ~slot'~.
|
|
|
|
En voici un exemple d'utilisation:
|
|
|
|
#+BEGIN_SRC purescript
|
|
type ChildQuery = Coproduct2 Alerts.Query Bot.Query
|
|
type ChildSlot = Either2 AlertsSlot BotSlot
|
|
|
|
HH.div_ [ HH.slot'
|
|
CP.cp1
|
|
(AlertsSlot unit)
|
|
Alerts.alertsComponent st.newAlert absurd
|
|
, HH.slot'
|
|
CP.cp2
|
|
(BotSlot b.name)
|
|
(botComponent initInfo)
|
|
unit
|
|
(HE.input (HandleBotMessage b.name))]
|
|
#+END_SRC
|
|
|
|
le type de ~slot'~ est:
|
|
|
|
#+BEGIN_SRC purescript
|
|
slot' :: forall f g g' p p' i o m.
|
|
ChildPath g g' p p'
|
|
-> p
|
|
-> Component HTML g i o m
|
|
-> i
|
|
-> (o -> Maybe (f Unit))
|
|
-> ParentHTML f g' p' m
|
|
#+END_SRC
|
|
|
|
#+BEGIN_QUOTE
|
|
Defines a slot for a child component when a parent has multiple types of child
|
|
component. Takes:
|
|
|
|
- the ChildPath for this particular child component type
|
|
- the slot "address" value
|
|
- the component for the slot
|
|
- the input value to pass to the component
|
|
- a function mapping outputs from the component to a query in the parent
|
|
#+END_QUOTE
|
|
|
|
Le ~ChildPath~ c'est l'équivalent d'un truc qui marche pour Lens et Prism ;). en
|
|
gros le ~CP.cp1~ ça dit (le premier element du product ou de la somme selon mes
|
|
besoins). Et on voit que je ne file pas l'etat initial, mais seulement ce
|
|
qu'Halogen appelle un ~Input~.
|
|
|
|
Donc l'input c'est quelque chose qui sera utiliser pour engendrer l'état initial du
|
|
sous composant si nécessaire.
|
|
|
|
Honnêtement, il y a des avantages et des inconvénient.
|
|
L'inconvénient à mon avis c'est que c'est moins explicite.
|
|
|
|
L'avantage, c'est que ça parait plus modulaire.
|
|
|
|
*** =dispatchAction=
|
|
|
|
L'utilisation de dispatchAction est assez sympa. Elle permet de s'occuper de l'initializer
|
|
qui manque cruellement à Halogen pour l'instant, mais qui ne va pas tarder à arriver.
|
|
|
|
*** Remarque sur les logs
|
|
|
|
Il me semblerai plus judicieux de créer une fichier d'util pour faire les logs
|
|
qui soit facilement désactivable pour la mise en prod. C'est pas terrible de
|
|
conserver les logs dans la console du front. D'autant plus qu'il n'est pas
|
|
impossible que ça fasse bugger certain browser pourri comme IE. Dans tous les
|
|
cas, c'est pas cool et ça fait pas pro d'ouvrir la console en prod et de voir
|
|
plein de logs. Faut un mode où quand on build pour la prod, les logs soient
|
|
absent. Par contre aussi dans l'idéal ça serait pas mal d'avoir un système qui
|
|
centralise les call aux actions et qui enregistre ça dans le localStorage et
|
|
d'ajouter un bouton pour soumettre un bug et quand on clique sur le bouton, ça
|
|
récupère la liste des Events. Normalement à partir de ça, il y a moyen d'écrire
|
|
une fonction qui "rejoue" les events pour son app.
|
|
|
|
J'ai dit localStorage, mais, ça peut être n'importe quoi, même ne pas sortir du
|
|
purescript.
|
|
|
|
*** Routing
|
|
|
|
Le routing me semble très bien. rien à redire là dessus. L'usage direct du
|
|
=localStorage= me semble aussi très facile à obtenir, j'imagine via l'effect
|
|
~Dom~. Bon ma recommendation est à l'avenir d'éviter d'utiliser des JWT pour des
|
|
sessions et de plutôt utiliser des Session serveur via les cookies. Mais bon.
|
|
On peut survivre trèèèèèès longtemps sans ça. D'autant plus que je ne pense pas
|
|
que les données soient très critiques à protéger.
|
|
|
|
*** Composants
|
|
|
|
**** Projects
|
|
à faire.
|
|
|
|
**** GraphExplorer
|
|
|
|
L'utilisation de js externe semble en réalité identique entre Halogen et
|
|
Thermite. Et le code me semble clean.
|
|
|
|
**** Gargantext/Dashboard
|
|
|
|
Personnelement je ne connais pas les détails de react, j'ai toujours considéré
|
|
l'utilisation des features "avancées" comme un anti-pattern.
|
|
Du coups, je remarque immédiatement l'utilisation de =Props=.
|
|
Mon intuition me dit que c'est pas beau, et non nécessaire avec du purescript.
|
|
|
|
Cependant, il s'agit peut-être de la bonne façon de s'intégrer avec React. Le
|
|
fait qu'Halogen n'utilise pas react est pour moi un avantage. Mais j'imagine que
|
|
ça peut-être un inconvénient si un des but est de pouvoir exporter et publier
|
|
des composants react pour des 3rd party.
|
|
|
|
Donc je préfère ne pas m'étendre sur le sujet.
|
|
|
|
Je ne suis même pas certain que cette partie soit réellement utilisée.
|
|
|
|
**** Tab/TabView
|
|
|
|
Un poil difficile à lire. Mais c'est peut-être inévitable pour ce type de composant.
|
|
Cependant, je ne suis pas certain de bien voir pourquoi =Tab= est isolé de =TabView=.
|
|
|
|
Avec l'esprit halogen, j'imagine qu'il ne serait pas beaucoup plus facile à
|
|
écrire. Mais j'imagine ce serait plus lisible, puisqu'ici il faut gérer les vues
|
|
et les actions de façon explicite là où la notion de slot et de composant
|
|
halogen permet d'abstraire ces détails.
|