initial commit
This commit is contained in:
commit
4e1befea7b
17 changed files with 2059 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
ltximg
|
BIN
Archive.org.gpg
Normal file
BIN
Archive.org.gpg
Normal file
Binary file not shown.
BIN
Cisco.org.gpg
Normal file
BIN
Cisco.org.gpg
Normal file
Binary file not shown.
218
Deep-Learning.org
Normal file
218
Deep-Learning.org
Normal file
|
@ -0,0 +1,218 @@
|
|||
-- #+TITLE: Deep Learning Coursera
|
||||
-- #+AUTHOR: Yann Esposito
|
||||
#+STARTUP: latexpreview
|
||||
#+TODO: TODO IN-PROGRESS WAITING | DONE CANCELED
|
||||
#+COLUMNS: %TODO %3PRIORITY %40ITEM(Task) %17Effort(Estimated Effort){:} %CLOCKSUM %8TAGS(TAG)
|
||||
|
||||
* Plan
|
||||
|
||||
5 courses
|
||||
|
||||
** Neural Network and Deep Learning
|
||||
*** Week 1: Introduction
|
||||
*** Week 2: Basic of Neural Network programming
|
||||
*** Week 3: One hidden layer Neural Networks
|
||||
*** Week 4: Deep Neural Network
|
||||
** Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization
|
||||
** Structuring your Machine Learning project
|
||||
** Convolutional Neural Networks
|
||||
** Natural Language Processing: Building sequence models
|
||||
* DONE Neural Network and Deep Learning
|
||||
CLOSED: [2017-08-22 Tue 13:43]
|
||||
** Introduction
|
||||
|
||||
*** What is a neural network?
|
||||
|
||||
*** Supervised Learning with Neural Networks
|
||||
|
||||
- Lucrative application: ads, showing the add you're most likely to click on
|
||||
- Photo tagging
|
||||
- Speech recognition
|
||||
- Machine translation
|
||||
- Autonomous driving
|
||||
|
||||
|
||||
***** Convolutional NN good for images
|
||||
|
||||
***** Strutured data (db of data) vs Unstructured data
|
||||
|
||||
- Structured data: Tables
|
||||
- Unstructured data: Audio, image, text...
|
||||
|
||||
Computer are much better at interpreting unstructured data.
|
||||
|
||||
*** Why is Deep Learning taking off?
|
||||
|
||||
[[///Users/yaesposi/Library/Mobile%20Documents/com~apple~CloudDocs/deft/img/Scale%20drives%20deep%20learning%20progress.png]]
|
||||
|
||||
- Data (lot of data)
|
||||
- Computation (faster learning loop)
|
||||
- Algorithms (ex, use ReLU instead of sigma)
|
||||
** Geoffrey Hinton interview
|
||||
** Binary Classification
|
||||
|
||||
\[ (x,y) x\in \mathbb{R}^{n_x}, y \in {0,1} \]
|
||||
|
||||
$m$ training examples: $$ {(x^{(1)},y^{(1)}), ... (x^{(m)},y^{(m)})} $$
|
||||
|
||||
$$ m = m_{train} , m_{test} = #test examples $$
|
||||
|
||||
$$ X = [ X^{(1)} ... X^{(m)} ] is an n_x x m matrix $$
|
||||
$$ X.shape (n_x,m) $$
|
||||
|
||||
$$ Y = [ y^{(1)} ... y^{(m)} ] $$
|
||||
$$ Y.shape = (1,m) $$
|
||||
|
||||
** Logistic Regression
|
||||
|
||||
Given $X \in \mathbb{R}^{n_x}$ you want $\hat{y} = P(y=1 | X)$
|
||||
|
||||
Paramters: $w \in \mathbb{R}^{n_x}, b\in \mathbb{R}$
|
||||
|
||||
Output: $\hat{y} = \sigma(w^Tx + b) = \sigma(z)$
|
||||
|
||||
$$\sigma(z)= \frac{1}{1 + e^{-z}}$$
|
||||
|
||||
If $z \rightarrow \infty => \sigma(z) \approx 1$
|
||||
If $z \rightarrow - \infty => \sigma(z) \approx 0$
|
||||
|
||||
|
||||
Alternative notation not used in this course:
|
||||
|
||||
$X_0=1, x\in\mathbb{R}^{n_x+1}$
|
||||
$\hat{y} = \sigma(\Theta^Tx)$
|
||||
...
|
||||
|
||||
** Logistic Regression Cost Function
|
||||
|
||||
Search a convex loss function:
|
||||
|
||||
$L(\hat{y},y) = - (y\log(\hat{y}) + (1-y)\log(1-\hat{y}))$
|
||||
|
||||
If y = 1 : $L(\hat{y},y) = -\log\hat{y}$ <- want log\haty larg, want \hat{y} large
|
||||
If y = 0 : $L(\hat{y},y) = -\log\hat{y}$ <- want log (1-\hat{y}) large, want \hat{y} sall
|
||||
|
||||
Cost function: $$ J(w,b) = \frac{1}{m}\sum_{i=1}^mL(\hat{y^\{(i)}},y^{(i)}) = ... $$
|
||||
|
||||
** Gradient Descent
|
||||
|
||||
Minize $J(w,b)$
|
||||
|
||||
1. initialize w,b (generaly uses zero)
|
||||
2. Take a step in the steepest descent direction
|
||||
3. repeat 2 until reaching global optimum
|
||||
|
||||
Repeat {
|
||||
$w := w - \alpha\frac{dJ(w)}{dw} = w - \alpha\mathtext{dw}$
|
||||
}
|
||||
|
||||
** Derivatives
|
||||
** More Derivative Examples
|
||||
** Computaion Graph
|
||||
** Computing Derivatives
|
||||
** Computing Derivatives for multiple examples
|
||||
** Vectorization
|
||||
getting rid of explicit for loops in your code
|
||||
** Vectorizing Logistic Regression
|
||||
** Vectorizing Logistic Regression's Gradient Computation
|
||||
** Broadcasting in Python
|
||||
** Quick Tour of Jupyter / ipython notebooks
|
||||
** Neural Network Basics
|
||||
J = a*b + a*c - (b+c) = a (b + c) - (b + c) = (a - 1) (b + c)
|
||||
* TODO Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization
|
||||
** DONE Week 1: Setting up your Machine
|
||||
CLOSED: [2017-08-22 Tue 13:43]
|
||||
*** Recipe
|
||||
|
||||
If *High bias*? (bad training set performance?)
|
||||
Then try:
|
||||
- Bigger network
|
||||
- Training longer
|
||||
- (NN architecture search)
|
||||
Else if *High variance*? (bad dev set performance?)
|
||||
Then try:
|
||||
- More data
|
||||
- Regularization
|
||||
- (NN architecture search)
|
||||
|
||||
Deep learning, not much bias/variance tradeoff if we have a big amount of
|
||||
computer power (bigger network) and lot of data.
|
||||
*** Regularization
|
||||
**** Regularization: reduce variance
|
||||
- L2 regularization
|
||||
|
||||
λ / 2m || w ||_2 ^2
|
||||
|
||||
- L1 regularization: same with |w| instead of ||w||_2^2
|
||||
|
||||
|
||||
λ is a regularization parameter (in code named =lambd=)
|
||||
|
||||
Cost = J(w^[1], b^[1], ..., w^[L], b^[L]) = 1/m \sum L(^y(i), y(i)) + λ/2m \sum_l=1^L || W^[l] ||^2
|
||||
|
||||
|
||||
call the "Frobenius norm"
|
||||
|
||||
dW = from backprop + λ/m W^l
|
||||
|
||||
update W^l = W^l - αdW^l still works
|
||||
|
||||
Sometime L2 regularization called "weight decay".
|
||||
|
||||
**** Dropout Regularization
|
||||
|
||||
Eliminates nodes by layer randomly for each training example.
|
||||
|
||||
- implementing, (inverted dropout)
|
||||
- gen random boolean vector:
|
||||
d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep_prob # (for each iteration)
|
||||
a3 = np.mulitply(a3,d3)
|
||||
a3 /= keep_prob (for normalization to be certain the a3 output still the same, reduce testing problems)
|
||||
|
||||
Making prediction at test time: no drop out
|
||||
|
||||
**** Over regularization methods
|
||||
|
||||
- Data augmentation, (flipping images for example, random crops, random distortions, etc...)
|
||||
- Early stopping, stop earlier iteration
|
||||
|
||||
*** Setting up your optimization problem
|
||||
|
||||
**** Normalizing Inputs
|
||||
|
||||
- μ = 1/m Sum X^(i)
|
||||
- x := x - μ (centralize)
|
||||
- σ = 1/m Sum X^(i)^2
|
||||
- x /= σ^2
|
||||
|
||||
|
||||
**** Gradient Checking
|
||||
***** Don't use gard check in traingin, only in debug
|
||||
***** If algorithm fail, grad check, look at component (is db? dW? dW on certain layer, etc...)
|
||||
***** Remember regularization
|
||||
***** Doesn't work with dropout, turn off drop out (put 1.0) then check
|
||||
***** Run at random initialization; perhaps again after training
|
||||
|
||||
** DONE Week 2: Optimization Algorithms
|
||||
CLOSED: [2017-08-22 Tue 13:43]
|
||||
*** Mini batch
|
||||
|
||||
X :: X^(1) ... X^(m)
|
||||
|
||||
X,Y -> X^{i},Y^{i} where X^{i} = X^(i*batch-size ---> (i+1)*batch-size)
|
||||
|
||||
*** Minibatch size
|
||||
|
||||
- if mini batch size = m => Batch gradient descent (X^{1},Y^{1}) = (X,Y)
|
||||
- if mini match size = 1 => Stochastic gradient descent, every example is its own mini batch.
|
||||
- in practice in between 1 and m, m --> too long, 1 loose speedup from vectorization.
|
||||
+ vectorization ~1000
|
||||
|
||||
1. If small training set, use batch gradient descent (m <= 2000)
|
||||
2. Typical mini-batch size: 64, 128, 256, 512, ... 2^k to fits in CPU/GPU memory
|
||||
|
||||
*** Exponentially weighted average
|
||||
|
||||
v_t = βv_{t-1} + (1-β)θ_t
|
||||
** TODO Week 3
|
||||
** TODO Week 4
|
Binary file not shown.
After Width: | Height: | Size: 392 KiB |
Binary file not shown.
After Width: | Height: | Size: 392 KiB |
296
Nicolas.org
Normal file
296
Nicolas.org
Normal file
|
@ -0,0 +1,296 @@
|
|||
#+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)
|
||||
** 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 ????
|
65
TODO.org
Normal file
65
TODO.org
Normal file
|
@ -0,0 +1,65 @@
|
|||
#+Title:TODO
|
||||
#+Author: Yann Esposito
|
||||
#+TODO: TODO IN-PROGRESS WAITING | DONE CANCELED
|
||||
#+COLUMNS: %TODO %3PRIORITY %40ITEM(Task) %17Effort(Estimated Effort){:} %CLOCKSUM %8TAGS(TAG)
|
||||
|
||||
|
||||
* TODO [#C] FP Meetup :geek:
|
||||
DEADLINE: <2017-10-30 Mon>
|
||||
** DONE Presentation History of FP, etc...
|
||||
CLOSED: [2017-06-21 Wed 08:09]
|
||||
* TODO [#C] Reddit Bot with GUI :geek:
|
||||
** TODO [#C] Configuration Form :ps:
|
||||
:PROPERTIES:
|
||||
:EFFORT: 4:00
|
||||
:END:
|
||||
#+BEGIN_SRC purescript
|
||||
type UserConf = UserConf
|
||||
{ subreddit :: Text
|
||||
, regex :: Regex
|
||||
, reply :: Text
|
||||
, enabled :: Bool
|
||||
}
|
||||
data Stored a = Stored { id :: ID, stored :: a }
|
||||
#+END_SRC
|
||||
** TODO [#C] Bot listen to HTTP Calls :hs:
|
||||
:PROPERTIES:
|
||||
:EFFORT: 4:00
|
||||
:END:
|
||||
*** TODO =POST /conf= create new conf
|
||||
- body params subreddit, regex, reply
|
||||
*** TODO =PUT /conf= update conf
|
||||
- body params subreddit, regex, reply
|
||||
*** TODO =GET /confs= retrieve all confs (can filter on subreddit)
|
||||
- query params subreddit
|
||||
*** TODO =DELETE /conf/conf=
|
||||
** TODO [#C] User Auth :hs:
|
||||
:PROPERTIES:
|
||||
:EFFORT: 16:00
|
||||
:END:
|
||||
** TODO [#C] Install and let it run on shoggoth1 :ops:
|
||||
:PROPERTIES:
|
||||
:EFFORT: 2:00
|
||||
:END:
|
||||
* DONE Impots :admin:
|
||||
CLOSED: [2017-06-21 Wed 07:53]
|
||||
** DONE Vérifier Revenu Cisco 2016-12: 52407€
|
||||
CLOSED: [2017-05-17 Wed 21:43]
|
||||
** DONE Vérifier décalaration revenus auto-entrepreneur
|
||||
CLOSED: [2017-04-19 Wed 12:24]
|
||||
** (ABC-Cours) 1794 + (O2) 931.19 = 2725
|
||||
** Frascati
|
||||
*** Intérêts d'emprunts: 7193,9
|
||||
*** Où déclarer le max - garage?
|
||||
** DONE Cases Réduction Duflot
|
||||
CLOSED: [2017-05-17 Wed 21:43]
|
||||
* DONE Anniv K :family:
|
||||
CLOSED: [2017-05-02 Tue 13:55]
|
||||
** Nous (4 adultes)
|
||||
** Audrey (2 + 1 enfant)
|
||||
** Caro (2 adultes + 1 enfant)
|
||||
** Renée (1 adulte)
|
||||
** Laurent (1 adulte)
|
||||
** Sonia (1 adulte ou 1 adulte et deux enfant, ou 2 adultes et 4 enfants)
|
||||
** Guillaume & Emilie (2 adultes et 1 enfant)
|
||||
** TOTAL Adultes: 14 adultes + 5 enfants.
|
213
blog-FP-Survey.org
Normal file
213
blog-FP-Survey.org
Normal file
|
@ -0,0 +1,213 @@
|
|||
#+Title: Functional Programming Survey
|
||||
|
||||
* Intro (Definition / History)
|
||||
** FP definition
|
||||
*** Programming paradigm
|
||||
*** Avoids changing-state and mutable data
|
||||
*** Declarative programming, expression or declarations instead of statements
|
||||
*** Output value of a function depends *only* on the arguments that are input to the function
|
||||
*** Eliminating side-effects, easier to reason about code and predict behavior
|
||||
*** Functions are *first-class*
|
||||
which means that they are treated like any other values and can be passed as arguments to other functions or be returnred as a result of a function.
|
||||
** History
|
||||
*** λ-Calculus, Alonzo Church & Rosser 1936
|
||||
Typeless theory of functions.
|
||||
|
||||
A syntaxic construction can be equivalent to any Turing Machine.
|
||||
Mainly re-writing rules.
|
||||
**** Learn λ-Calculus in less than 1 minute
|
||||
***** Definitions
|
||||
- =a,b,...= are variables
|
||||
- =()= parenthesis to group part of an expression
|
||||
- =λ= greek letter (pronounced Lambda) and the =.= to write functions.
|
||||
***** Free vs Bound
|
||||
- =λx.xy= the expression is open as =xy= contains a =y= and there is no =λy= that bound it.
|
||||
- =λx.λy.xy= is said to be bound.
|
||||
|
||||
Bound expressions represent functions.
|
||||
|
||||
***** Rules
|
||||
|
||||
(α) λx.A → λy.[y/x]A
|
||||
(β) (λx.A)B → λy.[B/x]A
|
||||
(η) λx.Ax → A if x not free in A
|
||||
|
||||
Where =[B/x]A= means substitue B for free occurrences of x in A.
|
||||
|
||||
***** How does that work? Simply cut and paste! That's it
|
||||
|
||||
Example: =(λy.x(yz))(ab)=
|
||||
|
||||
So =(ab)= is an expression we apply to the function =(λy.x(yz))=.
|
||||
So replace =y= by =ab= and we get: =x(abz)=.
|
||||
|
||||
And...
|
||||
|
||||
That's it.
|
||||
|
||||
*** LISP (McCarthy 1960)
|
||||
The first functional programming language and the second oldest programming
|
||||
language stil in use (after FORTRAN).
|
||||
|
||||
**** S-Language
|
||||
- atom which are words like X or TWO
|
||||
- pairing operation written as a dot
|
||||
|
||||
=((X.Y).Z)=
|
||||
=(ONE.(TWO.(THREE.NIL)))=
|
||||
|
||||
List, Trees, etc..
|
||||
|
||||
**** M-Language
|
||||
defines computations on S-exrepssions. It has
|
||||
- S-expressions
|
||||
- function application (=f[a;b;...]=) with primitve functions =cons=, =car=, =cdr=, =atom=, =eq=
|
||||
- conditional expressions (=[ test1 -> result1 ; test2 -> result2 ; ... ]=)
|
||||
- ability to define recursive functions (=first[x] = [atom [x] -> x ; T -> first[car[x]]]=)
|
||||
|
||||
**** Encoding M-Language expressions and functions as S-Expressions
|
||||
define M-language functions =eval= and =apply= that correctly interpret these S-expressions
|
||||
|
||||
Thus LISP allow meta-programming: treating program as data and vice versa
|
||||
|
||||
**** LISP Syntax so much parenthesis
|
||||
|
||||
It was intended to code use M-language in an Algol-like notation.
|
||||
In practice LISP wrote their code directly in S-expressions.
|
||||
M-language became a kind of ghost... theoretically important but not used by anyone.
|
||||
|
||||
*** Algol 60 (Naur et al 1963)
|
||||
*** ISWIM (Landin 1966)
|
||||
*** PAL (Evans 1968)
|
||||
*** SASL (1973-83)
|
||||
*** Edinburgh (1969-80) -- NPL, early ML, HOPE
|
||||
- NLP strongly typed but no polymorphisms, call-by-value
|
||||
- NLP evolved into HOPE: higher order, strongly typed with explicit types and
|
||||
polymorphic type variables, purely functional.
|
||||
- ML (Meta-Language) emerged as the meta-language of Edinburgh LCF, programmable verification system
|
||||
- Standard ML (Milner et al. 1990) pattern matching and type inference but _not pure_.
|
||||
*** Miranda (1986)
|
||||
- Milner type discipline: ~tree * :: Leaf * | Node (tree *) (tree *)~
|
||||
- Use of =*=, =**=, =***=, ... syntax as type variables from original ML
|
||||
*** Haskell (1992...)
|
||||
Similar to Miranda but richer and more redundant syntax.
|
||||
+ type classes
|
||||
+ monadic IO
|
||||
+ module system
|
||||
|
||||
* Major Principles (Theoretical)
|
||||
** Higher-order functions
|
||||
** Purity
|
||||
*** Function vs Procedure/Subroutines
|
||||
**** Referential Transparency vs Referential Opacity
|
||||
- Ability to copy/paste without changing result vs impossible
|
||||
- Help the programmer but also the compiler!
|
||||
- Help in proving correctness
|
||||
- Help simplifying algorithms, better linters
|
||||
- Optimizing by memoization, common subexpression elimination, lazy evaluation, parallelization
|
||||
**** Parallel computation become extremely easier to achieve
|
||||
***** Evaluation strategies:
|
||||
****** =(h (f a) (g b))= We can evaluate: =a= then =(f a)= then =b= then =(g b)= and finally =(h (f a) (g b))=
|
||||
****** =(h (f a) (g b))= We can evaluate: =b= then =a= then =(g b)= then =(f a)= and finally =(h (f a) (g b))=
|
||||
****** =(h (f a) (g b))= We can evaluate: =a= and =b= in parallel then =(f a)= and =(g b)= in parallel and finally =(h (f a) (g b))=
|
||||
****** =(h (f a) (g b))= We can evaluate: =h= and then evaluate =(f a)= only if needed and then =(g b)= only if needed...
|
||||
That's called non-strict evaluation (sometime lazy evaluation)
|
||||
For example if: =(def h (λx.λy.(+ x x)))= we don't need to evaluate =y=, in our case =(g b)=
|
||||
**** Time is Hard, purity remove time from the paradigm
|
||||
Calling the same function with the same parameter twice will always results in the same value.
|
||||
*** Effects?
|
||||
**** Side effects in the language
|
||||
The programmer must be careful not to use impure functions in place where only pure functions are expected.
|
||||
**** Split impurity using a flag (generaly a type)
|
||||
- =foo : Int -> String= declared as pure
|
||||
- =foo : Int -> IO String= declared as impure (IO)
|
||||
*** Exceptions / Errors?
|
||||
We can't avoid the fact real world has limits! Space limit, maybe time limit,
|
||||
maybe access limitations...
|
||||
** Tail Recursion
|
||||
Recursion is heavily used in functional programming. Functional language will
|
||||
often include *tail call optimisation* to ensure that heavy recursion does not
|
||||
consume excessive memory.
|
||||
** Functional Programming Style and Grey area Languages
|
||||
|
||||
Functional Style can be achieved in most languages. For example in Perl, PHP,
|
||||
C++11, Java 8 and C# 3.0. all added features to facilitate the functional style.
|
||||
|
||||
Scala is special as it is frequently written in a functional style but the
|
||||
presence of side effects and mutable state place it in a grey area.
|
||||
|
||||
*** A Hole in purity, and all great properties of functional programming fall appart
|
||||
|
||||
This is a major point. You cannot compromise with purity without losing
|
||||
everything. Extremely well explained in the SICP when he introduce the =set!=
|
||||
ability to *mutate* the value associated to a variable.
|
||||
|
||||
*** Grey Area Languages
|
||||
**** XSLT
|
||||
**** R, J, K and Q
|
||||
**** SQL (declarative)
|
||||
**** Lex/Yacc a bit
|
||||
|
||||
** Meta-Programming and Macros!
|
||||
|
||||
If you really want to have the next power level, you can write macros, or what
|
||||
is mostly called meta-programming.
|
||||
|
||||
A notable thing to know about are LISP Macros.
|
||||
|
||||
** Type System
|
||||
First, If when I say static typing you think C, C++, Java... No, that's NOT THAT AT ALL!
|
||||
|
||||
Type Theory is a vast mathematical field, with lot of hard and incredible work.
|
||||
|
||||
Some language don't need type system at all. Typically most LISP are very close to a raw lambda calculus.
|
||||
|
||||
But there is a problem with untyped languages: ⊥
|
||||
|
||||
Even we provided purity, you can imagine a system that help you reason about
|
||||
your code even further by adding more meta informations.
|
||||
|
||||
Different type systems: Hindley Milner, Martin/Lof, HoTT
|
||||
|
||||
- Basic Types (same as in Java)
|
||||
- Parametric Types ⇒ many different polymorphisms to choose
|
||||
- ADT! ⇒ Algebraic Data Types (type with algebraic properties FTW!)
|
||||
- GADT! ⇒ Generalized Algebraic Data Types
|
||||
- Generic Programming (be able to understand that two data structure share the same underlying structure)
|
||||
- Typeclasses ⇒ Incredible ability to abstraction (Monoids, Functor, Applicative, Monads, Traversables...)
|
||||
|
||||
* Major Languages (Practical)
|
||||
|
||||
- We only talk about General purpose languages (not domain specific languages)
|
||||
- Prod ready or not is only regarding our current knowledge. And by prod ready
|
||||
it won't be enough to name less than 5 company using it for small project.
|
||||
- Prod ready mean used at 100% by a company earning money for some years, and
|
||||
used partially (more than 10%) by more than 10 company or having a big
|
||||
production project
|
||||
|
||||
** Not Pure
|
||||
*** Can be used in modern production env
|
||||
**** LISP Family
|
||||
***** Common Lisp
|
||||
***** Clojure (Generally pure as default data structure are pure)
|
||||
**** Not LISP & General Purpose Languages
|
||||
***** Erlang
|
||||
***** Mathematica
|
||||
***** OCaml
|
||||
***** F#
|
||||
*** Not suitable for production yet
|
||||
**** LISP Family
|
||||
***** Scheme
|
||||
***** Racket
|
||||
**** Not LISP, General Purpose Languages
|
||||
***** Erlang
|
||||
***** ML, Caml
|
||||
** Pure
|
||||
*** Prod ready
|
||||
**** Haskell
|
||||
**** Elm
|
||||
*** Not Prod ready yet
|
||||
**** Idris
|
||||
**** Frege
|
||||
**** Eta (Haskell 7.10 on the JVM) might be used in production but really recent
|
||||
**** Clean
|
94
blog-false-programmer-problems.org
Normal file
94
blog-false-programmer-problems.org
Normal file
|
@ -0,0 +1,94 @@
|
|||
#+Title:Dev problem that don't help progress
|
||||
|
||||
There are a lot of debates between devs. Some of them are useful because after
|
||||
some time some clear winner emerge. But some are just a matter of either
|
||||
personal preferences, or even worse, won't change the outcome.
|
||||
|
||||
Why such debate continu to live years after years is just a matter of friction.
|
||||
Because my personal choices influence yours. For example, if I chose to use some
|
||||
editor code style by using spaces and you prefer to use tabs. We have a problem,
|
||||
and as each one of us want to keeps its habits, we might try to rationalize our
|
||||
choices. While really it is just a matter of personal preference. And so, in the
|
||||
end we should decide which one win.
|
||||
|
||||
So if you don't want to lose your time by searching to optimize your life here
|
||||
are the conclusions before the debate.
|
||||
|
||||
* Trivial Debates
|
||||
** Tools & Habits
|
||||
*** vim vs emacs vs any editor
|
||||
matter of personal preferences, I switched to vim keybinding mostly to prevent
|
||||
hand problems, and text editing might be slighly faster at the cost of a long
|
||||
training
|
||||
*** font choice
|
||||
**** Edit code
|
||||
Simply chose a monospaced font that make a clear distinction between:
|
||||
|
||||
- `0` and `O`
|
||||
- `1` and `l`
|
||||
- ``` and `'`
|
||||
- `''` and `"`
|
||||
- `1` and `i`
|
||||
- `8`, `B`, `6` and `0`
|
||||
- `2` and `Z`
|
||||
- `5` and `S`
|
||||
- `|` and `!`
|
||||
- `()` and `{}`
|
||||
- `:`, `;` and `i`
|
||||
- `.` and `,`
|
||||
|
||||
**** Website design
|
||||
|
||||
If you're not a designer don't over think about it.
|
||||
Just chose one preferred Sans serif and Serif font.
|
||||
|
||||
*** color scheme choice
|
||||
You should really use a low contrast colortheme if you want to minimize
|
||||
headhache and there are good chances you'll end up preferring dark themes.
|
||||
|
||||
*** tabs vs spaces
|
||||
Spaces appear to win slightly because the file size is not really important and
|
||||
most people don't care.
|
||||
|
||||
Smart tabs have still some issues with alignment.
|
||||
|
||||
*** OS choice for working
|
||||
Matter of personal preference
|
||||
|
||||
*** Typed (static) vs Unityped (dynamic) programming language
|
||||
I've got a long answer here, but if you are a proponent to unityped programming
|
||||
(dynamic typed programming) then you might not know language with great typing
|
||||
system.
|
||||
|
||||
If you are a proponent to static typing programming then know you can live using
|
||||
unityped programming.
|
||||
|
||||
The long answer being. Types are another abstraction. So as all abstraction, it
|
||||
has some benefits and some costs. I tend to believe that once you have finished
|
||||
your Proof of Concept Prototype, Types provide a lot more benefits than
|
||||
drawbacks. You can think about them as free unit testing. In fact with a complex
|
||||
enough type system you can think about them as an infinite number of unit tests
|
||||
for free. But just know that event with advanced typing system doesn't prevent
|
||||
you to write tests. But the opposite is also false, you can't simulate easily a
|
||||
typing system with only tests, even generative testing.
|
||||
|
||||
* My Choices
|
||||
** editor: spacemacs (best of vim and emacs)
|
||||
** font choice: Menlo (on OS X, Hack on other OS)
|
||||
** color scheme: solarized dark (each time I try to change I came back to it)
|
||||
** tabs vs spaces: spaces (no configuration pb, file size doesn't matter today)
|
||||
** Mac OS X: best for working, better focus, minimal configuration, setup time (would love a \*Nix env)
|
||||
** configurations/dotfiles: yadm
|
||||
** CVS: git with github (it's a social network)
|
||||
** typed vs untyped: typed help think right, but untyped is not _that_ bad.
|
||||
** Todo list, timers, note taking, thought orgnaiser: `org-mode`
|
||||
|
||||
* Solved but not known enough
|
||||
** REST (not RESTful)
|
||||
- Why REST: least surprise
|
||||
|
||||
** Encoding
|
||||
- Use UTF-8 Everywhere <http://utf8everywhere.org>
|
||||
|
||||
** Readability:
|
||||
- lenght of line (33em)
|
62
blog-on-SCRUM.org
Normal file
62
blog-on-SCRUM.org
Normal file
|
@ -0,0 +1,62 @@
|
|||
#+Title:On Scrumm
|
||||
|
||||
It is of good taste these day to critique Scrumm.
|
||||
Here are my 2cents
|
||||
|
||||
* Personal Experience with Scrum:
|
||||
** Discovering
|
||||
- Fear and counter arguments
|
||||
** Efficiency
|
||||
- Time lost in meeting
|
||||
** People tensions
|
||||
- instead of tamming it made everything worse.
|
||||
* Management: The root of all evil?
|
||||
The root of the problem is between the developpers and the managers.
|
||||
- Manager: I want a great product, I want to finish it fast, I want my customers to love it
|
||||
- Developer: I want a great product, I want to finish it fast, I want customers to love it
|
||||
|
||||
What could go wrong?
|
||||
|
||||
- What Manager means: _I want a great product_:
|
||||
I want to sell it!
|
||||
- What Developer means: _I want a great product_:
|
||||
I want it to use the last technology, with the last code organization/quality trends
|
||||
|
||||
- Manager: _I want to finish it fast_:
|
||||
I don't want to listen to technical discussion,
|
||||
this looks like something easy to do.
|
||||
It should be in my hand in few weeks.
|
||||
- Developer: _I want to finish it fast_:
|
||||
I want to keep the code clean to be able to add new changes fast
|
||||
with confidence (without breaking anything)
|
||||
this certainly means, testing + testing environment + proofs ...
|
||||
|
||||
- Manager: _I want customer to love it_:
|
||||
They should buy more and more. The product should be useful.
|
||||
- Developer: _I want customer to love it_:
|
||||
User should enjoy using it. The product should be simple, clean, natural, beautiful.
|
||||
|
||||
The two meaning are'nt completely opposite.
|
||||
Still they are quite different.
|
||||
|
||||
* What could we do about it?
|
||||
|
||||
How to solve the problem?
|
||||
- Spoiler: _you can't_.
|
||||
|
||||
The root of all evil is "it looks easy, do it".
|
||||
Proof, a guy made the same thing in PHP in 2003, or I saw the same shit in Flash around 1998.
|
||||
|
||||
You have two choices:
|
||||
|
||||
1. Use tools to finish your work fast, but the cost is very to maintain and modify.
|
||||
2. Use tools that enforce quality, you'll have a starting cost.
|
||||
|
||||
Error not to do:
|
||||
- Manager ask for something, you use your l337 H4X0R cape and you show him
|
||||
something in less than 10 minutes.
|
||||
- Manager believe that everything should be as fast
|
||||
- Discover what you did was just a terrible hack, and take 2 days to finish it
|
||||
correctly.
|
||||
- Manager doesn't really understand why he _saw_ it right now, and has to wait
|
||||
many days to really have it in production.
|
69
blog-programming-choices.org
Normal file
69
blog-programming-choices.org
Normal file
|
@ -0,0 +1,69 @@
|
|||
#+Title: Programming experiences and choices
|
||||
#+Author: Yann Esposito
|
||||
#+Language: English
|
||||
#+Select_tags: Programming, culture
|
||||
|
||||
* TODO Introduction
|
||||
|
||||
Why each programmer tend to prefer some programming language to solve its problems.
|
||||
How are we creating our preferrences?
|
||||
Why one use vim and the other one can use a specialized IDE?
|
||||
|
||||
* Preferences depends on experiences
|
||||
** Hard to understand
|
||||
** Goals
|
||||
* Back Story
|
||||
** At first
|
||||
|
||||
- Logo when I was 10yo at school
|
||||
- Basic when I was 11yo, with a book trying to draw lines and make games
|
||||
- Basic with Amtrad CPC 6128, trying to write games from magasines
|
||||
- Compiled Basic with Atari STe, write a game you are the hero in it.
|
||||
- Take some courses of beginner Pascal at school, sort algorithms
|
||||
** Math Background then Computer Science
|
||||
- Pascal for algorithmic
|
||||
- C, for basics, system and network
|
||||
- C++, Eiffel for Object Oriented Programming
|
||||
- A little bit of CaML (write a mathematical expression simplifier making big
|
||||
usage of pattern matching)
|
||||
** Ph. D. In Machine Learning
|
||||
- Give courses of Logo, C, etc...
|
||||
- Write lot of complex HMM related algorithms in C++ with quite complex ML algorithms
|
||||
- Discover Java and its promises, play a bit with it
|
||||
- Have friend that use CaML for its Ph.D. and its hash-maps are said to be
|
||||
faster than C. I remember this.
|
||||
** No Love for Machine Learning in 2005 :/
|
||||
- Write a Java program with an User Interface in my Post Ph. D. 1/2 of the work
|
||||
in the UI, the other half in the algorithm.
|
||||
** Still no love for ML in 2006 Find a job just to eat
|
||||
- Go find a job to eat, have a *lot* of time to learn new things
|
||||
- Discover HN, /r/programming, etc...
|
||||
- Web Applications are all the rage
|
||||
- DCVS is still a thing, people argue between git, mercurial, bazaar, etc...
|
||||
- Write a tremendous number of zsh scripts to handle a huge number of files, use Perl, etc...
|
||||
** First try at startup
|
||||
- Decide with a friend to make a product, choice of technology with them is _very_ hard.
|
||||
- git for example was a question
|
||||
- using FB connect instead of classical, name / password bullshit is refused
|
||||
because they still live technically in 2000
|
||||
- Design decisions are hard to make
|
||||
- Programming language, I heard good things about OCaML as the fastest high
|
||||
level language. Can't even talk about it.
|
||||
- A guy need technicians to make its product and is willing to pay.
|
||||
- OK good first thing to try ourselves.
|
||||
- The other guy know Ruby, so let's go with Ruby (no rails)
|
||||
- Write our own framework, many technical frictions, but in the end a PoC is
|
||||
made full ruby, deployed on heroku. The product is an end-to-end personal
|
||||
electrical power consumption system.
|
||||
- Guy explain, I want "real time"!!!! ???? WTFBBQ!! Real time is way harder than
|
||||
just drawing dashboards!!!!
|
||||
- Have performance problems! Start looking into other frameworks, stumble upon
|
||||
snap, an Haskell framework stating that it is _very_ fast.
|
||||
- Start looking further into Haskell from there.
|
||||
** Haskell learning
|
||||
- Learn Haskell from web programming perspective, the goal is not to _learn_
|
||||
Haskell but to _use_ Haskell to write as fast as possible a web application
|
||||
with batteries included. After trying a bit, I choose to use Yesod.
|
||||
- Lot of time lost due to Yesod difficulties to handle correct package version coherence!!!!
|
||||
- cabal freeze, etc... to the rescue, not perfect but ok, able to deploy on heroku
|
||||
- Learn Haskell in the process.
|
504
blog-raw-haskell-web-app.org
Normal file
504
blog-raw-haskell-web-app.org
Normal file
|
@ -0,0 +1,504 @@
|
|||
#+Title: Haskell Web Application from scratch
|
||||
#+Author: Yann Esposito
|
||||
|
||||
* Introduction
|
||||
|
||||
** Functional Programming oriented to make a web application
|
||||
|
||||
** Tooling choices
|
||||
|
||||
- macOS Sierra
|
||||
- spacemacs
|
||||
- stack (not using ghc-8.0.1, there is a bug with macOS)
|
||||
|
||||
** High quality application
|
||||
|
||||
Even if an application only print "hello world" there are a lot of subtle way
|
||||
such an app could fail or have problems. See for example the [changelogs to GNU
|
||||
Hello](https://github.com/avar/gnu-hello/blob/master/ChangeLog).
|
||||
|
||||
The goal of this tutorial is not to provide a "see what we can do with Haskell"
|
||||
but more, how could we enforce production quality development with Haskell.
|
||||
Unfortunately, the tooling is very important in these matters.
|
||||
|
||||
To reach such goal we should at least provide:
|
||||
|
||||
- Documentation
|
||||
- Unit Tests
|
||||
- Generative Tests
|
||||
- Benchmarks
|
||||
- Profiling
|
||||
- CI
|
||||
- Deployment
|
||||
|
||||
It's easy to have one tutorial for each of these concepts, here that won't be a
|
||||
deep dive, but a first introduction on how to achieve all these goals.
|
||||
|
||||
* Tooling and preparing to code
|
||||
|
||||
blog-image("stillsuit.jpg","Stillsuit")
|
||||
|
||||
** Warning
|
||||
|
||||
If you never installed Haskell before, it should be a bit long to setup a
|
||||
correct working environment. So please follow me, don't give up because
|
||||
something doesn't work the first time. I made my best to make my environment
|
||||
work for most people.
|
||||
|
||||
** Installing Haskell Compiler
|
||||
|
||||
Install Haskell etc... In my opinion the easiest way to start is to install =stack=.
|
||||
Then you need to choose a great name for your project, why not =shai-hulud=?
|
||||
|
||||
blog-image("shai-hulud.jpg","Shai Hulud")
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
> stack new shai-hulud tasty-travis
|
||||
#+END_SRC
|
||||
|
||||
Yeah now you have a new directory, let use git:
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
> cd shai-hulud
|
||||
> git init .
|
||||
#+END_SRC
|
||||
|
||||
Now we have some source code, let's try it[^1].
|
||||
|
||||
[^1]: If you are on a Mac, please, modify the line =resolver: lts-7.18= by
|
||||
=resolver: nightly-2017-01-25= to be certain to use =ghc-8.0.2= because there is
|
||||
a bug with =ghc-8.0.1=.
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
> stack setup && stack build && stack exec -- shai-hulud-exe
|
||||
42
|
||||
#+END_SRC
|
||||
|
||||
** Dependencies & Libraries
|
||||
|
||||
As we want to make a web application let's add the needed dependencies to help
|
||||
us. Typically we want a web server
|
||||
[warp](https://hackage.haskell.org/package/warp) and also a Web Application
|
||||
Interface [WAI](https://hackage.haskell.org/package/wai). We'll also need to use
|
||||
[http-types](https://hackage.haskell.org/package/http-types).
|
||||
|
||||
In the =shai-hulud.cabal= file, in the =shai-hulud-exe= section, add to the
|
||||
build-depends =http-types=, =wai= and =warp= like this:
|
||||
|
||||
#+BEGIN_SRC
|
||||
executable shai-hulud-exe
|
||||
default-language: Haskell2010
|
||||
ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N
|
||||
hs-source-dirs: src-exe
|
||||
main-is: Main.hs
|
||||
build-depends: base >= 4.8 && < 5
|
||||
, shai-hulud
|
||||
, http-types
|
||||
, wai
|
||||
, warp
|
||||
#+END_SRC
|
||||
|
||||
Then we modify the =src-exe/Main.hs= file to contains:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Network.Wai
|
||||
import Network.HTTP.Types
|
||||
import Network.Wai.Handler.Warp (run)
|
||||
|
||||
app :: Application
|
||||
app _ respond = do
|
||||
putStrLn "I've done some IO here"
|
||||
respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!"
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "http://localhost:8080/"
|
||||
run 8080 app
|
||||
#+END_SRC
|
||||
|
||||
We'll go in detail later about what everything means.
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
> stack build && stack exec -- shai-hulud-exe
|
||||
...
|
||||
... Lot of building logs there
|
||||
...
|
||||
http://localhost:8080/
|
||||
#+END_SRC
|
||||
|
||||
Yeah! It appears to work, now let's try it by going on <http://localhost:8080/>
|
||||
in a web browser. You should see =Hello, Web!= in your browser and each time you
|
||||
reload the page a new message is printed in the console because some IO were performed.
|
||||
|
||||
** So can we start yet?
|
||||
|
||||
Hmmm no sorry, not yet.
|
||||
|
||||
|
||||
We should not use the default prelude.
|
||||
|
||||
While this article is a tutorial, it is not exactly a "very basic" one. I mean,
|
||||
once finished the environment would be good enough for production. There will be
|
||||
tests, ability to reproduce the build on a CI, and so, for such a program I
|
||||
should prevent it to have runtime errors.
|
||||
|
||||
In fact, certainly one of the main reason to use Haskell is that it helps
|
||||
prevent runtime errors.
|
||||
|
||||
In order to do that we'll use a prelude that doesn't contain any partial
|
||||
functions. So I choosed to use =protolude=[^2].
|
||||
|
||||
|
||||
For that that's quite easy, simply add =protolude= as a dependency to your cabal file.
|
||||
We'll modify the cabal file that way:
|
||||
|
||||
#+BEGIN_SRC
|
||||
library
|
||||
default-language: Haskell2010
|
||||
ghc-options: -Wall -Werror -O2
|
||||
hs-source-dirs: src
|
||||
exposed-modules: {-# higlight #-}Lib{-# /highlight #-}
|
||||
, ShaiHulud.App
|
||||
build-depends: base >= 4.8 && < 5
|
||||
, http-types
|
||||
, protolude
|
||||
, wai
|
||||
|
||||
executable shai-hulud-exe
|
||||
default-language: Haskell2010
|
||||
ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N
|
||||
hs-source-dirs: src-exe
|
||||
main-is: Main.hs
|
||||
build-depends: shai-hulud
|
||||
, base >= 4.8 && < 5
|
||||
, http-types
|
||||
, protolude
|
||||
, wai
|
||||
, warp
|
||||
#+END_SRC
|
||||
|
||||
We move the =app= declaration in =src/ShaiHulud/App.hs=:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module ShaiHulud.App
|
||||
( app )
|
||||
where
|
||||
|
||||
import Protolude
|
||||
|
||||
import Network.Wai
|
||||
import Network.HTTP.Types
|
||||
|
||||
app :: Application
|
||||
app _ respond = do
|
||||
putText "I've done some IO here"
|
||||
respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!"
|
||||
#+END_SRC
|
||||
|
||||
And we remove it from =src-exe/Main.hs=:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
import Protolude
|
||||
|
||||
import Network.Wai.Handler.Warp (run)
|
||||
|
||||
import ShaiHulud.App (app)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putText "http://localhost:8080/"
|
||||
run 8080 app
|
||||
#+END_SRC
|
||||
|
||||
So now the tooling around being able to start working seems done.
|
||||
|
||||
** Not yet
|
||||
|
||||
Yes I talked about:
|
||||
- Installation with =stack= that should take care of installing Haskell
|
||||
- How to add dependencies by adding them to the cabal file
|
||||
- Sane prelude with =protolude=
|
||||
- Provided an overview of WAI Application type
|
||||
|
||||
But I forgot to mention part of the tooling that is generally very personal.
|
||||
I use spacemacs and to take advantages of many of the editor niceties
|
||||
I also use =intero= and =haddock=.
|
||||
|
||||
So other things to think about:
|
||||
|
||||
- Install =intero= with =stack install intero=.
|
||||
- Also generate hoogle documentation: =stack hoogle data=
|
||||
- You could also check the tests and benchmark suites: =stack test= and =stack bench=
|
||||
|
||||
** So we should be done with prelimiaries
|
||||
|
||||
So we should be done with preliminaries, at least, I hope so...
|
||||
|
||||
If you started from scratch it was certainly a terrible first experience. But
|
||||
be assured that once done, most of the step you've taken won't be needed for your next
|
||||
project.
|
||||
|
||||
* Web Application
|
||||
|
||||
So what is a web application?
|
||||
|
||||
** WAI
|
||||
|
||||
So if you look again at the code you see that your application main function
|
||||
simply print =http://localhost:8080/= and then run the server on the port =8080=
|
||||
using =app=.
|
||||
|
||||
The type of =app= is =Application=, if we look at the type of Application in
|
||||
WAI, for example by using =SPC-h-h= on the Application keyword or by going in
|
||||
the [WAI documentation](https://www.stackage.org/haddock/lts-7.18/wai-3.2.1.1/Network-Wai.html).
|
||||
|
||||
We see that:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
|
||||
#+END_SRC
|
||||
|
||||
Hmmmm.... What? So just remakr WAI is at it's third major version. So if we just
|
||||
take a look at WAI in its previous version we see that Application was defined
|
||||
as:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
type Application = Request -> IO Response
|
||||
#+END_SRC
|
||||
|
||||
Which look quite more intuitive. Because, what is the role of a web server if
|
||||
not sending response to requests? The IO here is just there to explain that in
|
||||
order to send a response the server might use IOs like reading in some DB or the
|
||||
file system.
|
||||
|
||||
So why let's take a bit to analyze the new definition of =Application= in WAI 3.
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
|
||||
#+END_SRC
|
||||
|
||||
It is explained:
|
||||
|
||||
#+BEGIN_QUOTE
|
||||
The WAI application.
|
||||
|
||||
Note that, since WAI 3.0, this type is structured in continuation passing style
|
||||
to allow for proper safe resource handling. This was handled in the past via
|
||||
other means (e.g., ResourceT). As a demonstration:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
app :: Application
|
||||
app req respond = bracket_
|
||||
(putStrLn "Allocating scarce resource")
|
||||
(putStrLn "Cleaning up")
|
||||
(respond $ responseLBS status200 [] "Hello World")
|
||||
#+END_SRC
|
||||
|
||||
#+END_QUOTE
|
||||
|
||||
Great, so before it was difficult to handling some resources, now it appears to
|
||||
be easier to write using =bracket_=. Hmm... =bracket_=? What is this function?
|
||||
If you search it in [hoogle](https://www.haskell.org/hoogle/?hoogle=bracket_):
|
||||
|
||||
OK that's quite easy, you see it is a function of =Control.Exception.Base= that
|
||||
we could use like this:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
bracket
|
||||
(openFile "filename" ReadMode)
|
||||
(hClose)
|
||||
(\fileHandle -> do { ... })
|
||||
#+END_SRC
|
||||
|
||||
And =bracket_= is a variation of =bracket= which doesn't need the return value
|
||||
of the first computation to be used the the "closing" computation.
|
||||
(More details here)[http://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Exception-Base.html#v:bracket_].
|
||||
|
||||
So ok, an =Application= is "mostly" a function that take a =Request= an returns
|
||||
an =IO Response=.
|
||||
|
||||
Good, now let's take another look to the =app= code:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
app :: Application
|
||||
app _ respond = do
|
||||
putText "I've done some IO here"
|
||||
respond $ responseLBS status200 [("Content-Type","text/plain")] "Hello, Web!"
|
||||
#+END_SRC
|
||||
|
||||
As you see we don't use the first parameter, the =Request=. So we could ask for
|
||||
some JSON data on =/foo/bar/= with a POST, it will still respond an HTTP 200
|
||||
with content-type plain text containing the body =Hello, Web!=.
|
||||
|
||||
So what a web app should provide. And here we could go down the rabbit hole of
|
||||
the HTTP standard and all its subtleties. But the first thing to come in mind is
|
||||
"how to handle routing"?
|
||||
|
||||
One of the advantage of using a language with some types flexibility is to use
|
||||
the types as a high level specification.
|
||||
|
||||
** Routing
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
data ShaiHuludApp = Routes -> Application
|
||||
#+END_SRC
|
||||
|
||||
That's easy, provided a "Routes" representation we should be able to "generate"
|
||||
a WAI Application. Now how should we represent a set of =Routes=?
|
||||
|
||||
We should split them by:
|
||||
|
||||
- HTTP Verb: =GET=, =POST=, =PUT=, =DELETE=, =HEAD=, =OPTIONS=, ...
|
||||
- Path: =/=, =/users/:userID= ...
|
||||
- Content-Type: =application/json=, =text/plain=, =text/html=, =text/css=,
|
||||
=application/javascript=...
|
||||
|
||||
Hmmm....
|
||||
|
||||
So it is immediately very difficult. And these are just the basic requirement,
|
||||
what about all subtelties about Standardized Headers (CORS, ETags, ...), Custom
|
||||
Headers...
|
||||
|
||||
** Is that FP compatible?
|
||||
|
||||
As a functional programmer, and more generally as a scientis, math lover I
|
||||
immediately dislike that profusion of details with a lot of ambiguities.
|
||||
|
||||
For example, REST is still ambiguous, should you use POST / PUT to update?
|
||||
Should you put a parameter in:
|
||||
- part of the path like =/user/foo=
|
||||
- in the query param of the URI =/user?userID=foo=
|
||||
- in the body? Then what parser should we use? FORM param, JSON, XML?
|
||||
- in the headers?
|
||||
- Why not as an anchor? =/user#foo
|
||||
- How should I provide a parameter that is a list? A set? A Hash-map? Something more complex?
|
||||
|
||||
The problem of web programming come from the tooling. Browsers and HTTP evolved
|
||||
together and some feature existed in browser before people had really the time
|
||||
to think corectly about them.
|
||||
|
||||
That's called real-life-production-world. And it sucks! For a better critique of
|
||||
web standards you should really read [the chapter «A Long digression into how
|
||||
standards are made» in Dive into
|
||||
HTML5](http://diveintohtml5.info/past.html#history-of-the-img-element).
|
||||
|
||||
So how could we get back our laws, our composability? Our maths and proofs?
|
||||
|
||||
We have a lot of choices, but unfortunately, all the tooling evolved around the
|
||||
existing standards. So for example, using GET will be cached correctly while
|
||||
POST won't. And a lot of these details.
|
||||
|
||||
*** FP Compatible Web Programming?
|
||||
|
||||
Let's re-invent web programming with all we know today.
|
||||
|
||||
First, one recent trends has changed a lot of things.
|
||||
Now a web application is splitted between a frontend and backend development.
|
||||
|
||||
The frontend development is about writing a complete application in a browser.
|
||||
Not just a webpage. The difference between the two notions is blurred.
|
||||
|
||||
Once consequence is that now, backend application should only present Web API
|
||||
and should never send rendering informations. Only datas. So this is a
|
||||
simplification, the backend should simply expose "procedures", the only things
|
||||
to think about are the size of the parameter to send and the size of the
|
||||
response. As every of these objects will go through the wire.
|
||||
|
||||
But there are interresting rules:
|
||||
|
||||
- =GET= for read only functions
|
||||
- =POST= generic read/write functions
|
||||
- =PUT= idempotent read/write functions
|
||||
- =DELETE= like =PUT= but can delete things
|
||||
|
||||
But there are also HTTP code with so many different semantics.
|
||||
|
||||
- =1xx=: technical detail
|
||||
- =2xx=: Successful
|
||||
- =3xx=: Redirection
|
||||
- =4xx=: Client Error
|
||||
- =5xx=: Server Error
|
||||
|
||||
So there are some similarities with the HTTP 1.1 reference and the control on
|
||||
functions we try to achieve with Haskell.
|
||||
|
||||
One thing I'd like to imagine is simply that a Web API should simply be like a
|
||||
library. We could simplify everything _a lot_ by removig most accidental
|
||||
complexity.
|
||||
|
||||
If we consider a web application to be split between a frontend application and
|
||||
a backend application it changes a lot of things. For example, we could mostly
|
||||
get rid of urls, we can consider to use the backend as a way to expose
|
||||
procedures.
|
||||
|
||||
Let's for example decide to use only POST, and send parameters only in the body.
|
||||
|
||||
In Haskell we could write:
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
foo :: IO X -- ⇒ POST on /foo
|
||||
bar :: A -> IO X -- ⇒ POST on /foo with a body containing an A
|
||||
#+END_SRC
|
||||
|
||||
And that's it.
|
||||
|
||||
* Appendix
|
||||
** Haskell Fragmentation vs Di
|
||||
|
||||
There are many other prelude, one of my personal problem with Haskell is fragmentation.
|
||||
|
||||
Someone could see "diversity" another one "fragmentation".
|
||||
|
||||
Diversity is perceived as positive while fragmentation isn't.
|
||||
|
||||
So is diversity imply necessarily fragmentation?
|
||||
Could we cure fragmentation by making it hard for people to compete?
|
||||
|
||||
I don't think so. I believe we could have the best of both world.
|
||||
|
||||
Then fragmentation occurs. And fragmentation is bad, because if you have an
|
||||
issue with your choice, the number of people that could help you is by nature
|
||||
reduced.
|
||||
|
||||
I would say that there is fragmentation when there is no one obvious choice. But
|
||||
having an obvious choice for a library for example doesn't really prevent
|
||||
diversity. Fragmentation:
|
||||
|
||||
- NixOS, raw cabal + Linux, stack
|
||||
- preludes
|
||||
- editor
|
||||
- stream library
|
||||
- orientation of the language "entreprisy ready, production oriented" make
|
||||
it work being dirty, add dirty choices for research people working in the
|
||||
language, "research oriented" make it beautiful or don't make it, block
|
||||
entreprisy people.
|
||||
|
||||
** =bracket_=
|
||||
|
||||
[^3]: Also if you are
|
||||
curious and look at its implementation it's quite short and at least for me,
|
||||
easy to inuit.
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
bracket :: IO a -- ^ computation to run first (\"acquire resource\")
|
||||
-> (a -> IO b) -- ^ computation to run last (\"release resource\")
|
||||
-> (a -> IO c) -- ^ computation to run in-between
|
||||
-> IO c -- returns the value from the in-between computation
|
||||
bracket before after thing =
|
||||
mask $ \restore -> do
|
||||
a <- before
|
||||
r <- restore (thing a) `onException` after a
|
||||
_ <- after a
|
||||
return r
|
||||
|
||||
bracket_ :: IO a -> IO b -> IO c -> IO c
|
||||
bracket_ before after thing = bracket before (const after) (const thing)
|
||||
#+END_SRC
|
||||
|
||||
Very nice
|
185
blog-transient.org
Normal file
185
blog-transient.org
Normal file
|
@ -0,0 +1,185 @@
|
|||
#+Title: Haskell Transient
|
||||
|
||||
* Transient Basic
|
||||
** Basic operators =async= and =(<|>)=
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Transient.Base (keep',async)
|
||||
import Transient.Move (local,onAll,runAt,lliftIO,Node,Cloud,addNodes,listen,createNode,runCloudIO)
|
||||
import Data.Monoid ((<>))
|
||||
import Control.Applicative ((<|>),empty)
|
||||
import Data.Foldable (traverse_)
|
||||
flapping :: IO ()
|
||||
flapping = keep' $ do -- keep' is just here to stranslate from TransIO to IO
|
||||
-- Inside this do we are in the TransIO Monad context
|
||||
x <- async (return "1") -- spawn another thread
|
||||
<|> async (return "2") -- spawn another thread
|
||||
<|> return "main thread MUST BE AT THE END!!!!!!" -- don't spawn any thread
|
||||
liftIO $ print x
|
||||
#+END_SRC
|
||||
|
||||
So this code spawn 2 threads, each printing something different. The first will
|
||||
print ="1"=, the second ="2"= and the main thread will print ="main thread MUST
|
||||
BE AT THE END!!!!!!!"=.
|
||||
|
||||
*** Intuition for =(<|>)=
|
||||
|
||||
=(<|>)= is an operator of choice on Applicative.
|
||||
To shut down all the abstraction bullshit.
|
||||
Let's just say that =(<|>)= operator is defined aside of an =empty= element.
|
||||
Just that:
|
||||
|
||||
#+BEGIN_SRC
|
||||
empty <|> x = x
|
||||
x <|> empty = x
|
||||
#+END_SRC
|
||||
|
||||
In TransIO monad context, it will choose the first non empty choice.
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
> Just "Hello" <|> Just "World"
|
||||
Just "Hello"
|
||||
|
||||
> Nothing <|> Just "World"
|
||||
Just World
|
||||
|
||||
> :t (<|>)
|
||||
(<|>) :: Alternative f => f a -> f a -> f a
|
||||
|
||||
-- Compare to this:
|
||||
> :t (<*>)
|
||||
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
|
||||
|
||||
> (,) <$> Just "Hello" <*> Just "World"
|
||||
Just ("Hello","World")
|
||||
|
||||
> (,) <$> Nothing <*> Just "World"
|
||||
Nothing
|
||||
#+END_SRC
|
||||
|
||||
*** Follow up
|
||||
|
||||
In the code example as =async= will return empty in the current process but not
|
||||
empty in the other process. The same code do two different things depending on
|
||||
the context. Exactly like =fork= in =C=.
|
||||
|
||||
* Working with multiple nodes
|
||||
** Launch a process on two external nodes
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
module Flapping
|
||||
(flapper
|
||||
,flapping)
|
||||
where
|
||||
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Transient.Base (TransIO)
|
||||
import Transient.Move (local,onAll,runAt,lliftIO,Node,Cloud,addNodes,listen,createNode,runCloudIO)
|
||||
import GHCJS.HPlay.View (div,id,span)
|
||||
import Data.Monoid ((<>))
|
||||
import Control.Applicative ((<|>),empty)
|
||||
import Control.Monad (mapM)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
-- creating two nodes on localhost
|
||||
node1 <- createNode "localhost" 20000
|
||||
node2 <- createNode "localhost" 20001
|
||||
runCloudIO $ do
|
||||
-- create 3 threads
|
||||
-- one listen node1
|
||||
-- the other listen node 2
|
||||
-- the last one is the current thread
|
||||
listen node1 <|> listen node2 <|> return ()
|
||||
-- on node1 return "hello"
|
||||
r1 <- runAt node1 (return "hello")
|
||||
-- on node2 return "world"
|
||||
r2 <- runAt node2 (return "world")
|
||||
-- the local thread print "hello world!"
|
||||
local $ liftIO $ putStrLn (r1 <> " " <> r2 <> "!")
|
||||
#+END_SRC
|
||||
|
||||
** Now with =n= nodes:
|
||||
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Transient.Base (TransIO)
|
||||
import Transient.Move (local,onAll,runAt,lliftIO,Node,Cloud,addNodes,listen,createNode,runCloudIO)
|
||||
import GHCJS.HPlay.View (div,id,span)
|
||||
import Data.Monoid ((<>))
|
||||
import Control.Applicative ((<|>),empty)
|
||||
import Control.Monad (mapM)
|
||||
import Data.Foldable (traverse_)
|
||||
|
||||
func = a -> m a
|
||||
func n = return ("received: " <> show n)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let nbNodes = 10
|
||||
-- create nbNodes which can receive orders to execute functions
|
||||
nodes <- traverse (createNode "localhost") [20000..(20000 + nbNodes - 1)]
|
||||
runCloudIO $ do
|
||||
-- make nbNodes threads listening to all created nodes
|
||||
foldl (<|>) empty (map listen nodes) <|> return ()
|
||||
r <- traverse (\n -> runAt (nodes !! n) (func n))
|
||||
[0..(fromIntegral nbNodes - 1)]
|
||||
-- local to go from TransIO -> Cloud
|
||||
-- liftIO to go from IO -> TransIO
|
||||
local $ liftIO $ traverse_ print r
|
||||
#+END_SRC
|
||||
|
||||
** More details
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE NoImplicitPrelude #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
module Flapping
|
||||
(flapping)
|
||||
where
|
||||
|
||||
-- We use protolude to make things safer by default
|
||||
-- and use less imports at the same time
|
||||
-- Yeah, Base.Prelude is pretty fucked up
|
||||
-- Not good for beginner neither for advanced users
|
||||
import Protolude hiding (async, local)
|
||||
|
||||
import Transient.Base (async, keep')
|
||||
import Transient.Move (Cloud, Node, addNodes, createNode, listen,
|
||||
lliftIO, local, onAll, runAt, runCloudIO)
|
||||
-- import GHCJS.HPlay.View (div,id,span)
|
||||
|
||||
func :: Int -> Cloud Text
|
||||
func n = return ("received: " <> show n)
|
||||
|
||||
flapping :: IO ()
|
||||
flapping = do
|
||||
let nbNodes = 100
|
||||
-- create nbNodes which can receive orders to execute functions
|
||||
nodes <- traverse (createNode "localhost") [20000..(20000 + nbNodes - 1)]
|
||||
runCloudIO $ do
|
||||
-- make nbNodes threads listening to all created nodes
|
||||
foldl (<|>) empty (map listen nodes) <|> return ()
|
||||
-- zip nodes ([1..] :: [Int]) => [(node1,1), (node2,2),...]
|
||||
-- then we use these couples of type (Node,Int) to run some process
|
||||
-- on another node
|
||||
-- the & just reverse the order of function application
|
||||
-- I prefer to say, take theses objects and do this thing to them
|
||||
-- instead of
|
||||
-- do this thing to all of theses objects
|
||||
r <- zip nodes ([1..] :: [Int]) &
|
||||
traverse (\(node,n) -> runAt node (func n))
|
||||
|
||||
-- local to go from TransIO -> Cloud
|
||||
-- liftIO to go from IO -> TransIO
|
||||
-- so
|
||||
-- local . liftIO :: IO -> Cloud
|
||||
-- is just here for the plumbing
|
||||
local . liftIO $ traverse_ putText r
|
||||
#+END_SRC
|
||||
|
||||
Results are in order, because traverse is sequential
|
BIN
img/Scale drives deep learning progress.png
Normal file
BIN
img/Scale drives deep learning progress.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 392 KiB |
51
org-mode-cheat-sheet.org
Normal file
51
org-mode-cheat-sheet.org
Normal file
|
@ -0,0 +1,51 @@
|
|||
#+Title: org-mode cheat sheet
|
||||
#+TODO: TODO IN-PROGRESS WAITING | DONE CANCELED
|
||||
|
||||
M = Command
|
||||
|
||||
* Style
|
||||
** *bold*
|
||||
** /italic/
|
||||
** _underlined_
|
||||
** =code=
|
||||
** ~verbatim~
|
||||
** +stroked+
|
||||
* Change Nodes Level
|
||||
** =M-RIGHT= : move one level deeper the current node
|
||||
** =M-S-RIGHT= : move one level deeper the current subtree
|
||||
* Move nodes
|
||||
** =M-UP= : move the node upper
|
||||
** =M-DOWN= : move the node one line lower
|
||||
** =M-A-UP= : move the subtree before the previous same level subtree
|
||||
** =M-A-DOWN= : move the subtree after the next same level subtree
|
||||
* Create Nodes
|
||||
** =C-RET= Insert new heading after subtree
|
||||
** =SPC m -= Turn (head)line into item (cycle item type)
|
||||
** =SPC m *= Turn item/line into headline
|
||||
* Editing
|
||||
** =SPC m n= narrow view to subtree
|
||||
** =SPC m N= widen view
|
||||
** =SPC m b= focus subtree buffer
|
||||
** =SPC m i l= insert link
|
||||
* work with todos
|
||||
** type =t= or =S-RIGHT= / =S-LEFT= to cycle from todo, done, nothing
|
||||
** =, T= show todo tree
|
||||
** TODO [#B] Full Task Example :tag1:tag2:
|
||||
DEADLINE: <2019-03-10>
|
||||
|
||||
:PROPERTIES: []
|
||||
:Effort: 2h
|
||||
:Cost: $200
|
||||
:END:
|
||||
|
||||
* Tables
|
||||
|
||||
#+PLOT: title:"example" ind:1 type:2d with:lines
|
||||
#+PLOT: labels:("first new label" "second column" "last column")
|
||||
#+TBLNAME:org-plot-example-1
|
||||
| var | x | y |
|
||||
|-----+----+-----|
|
||||
| 0.1 | -1 | 0 |
|
||||
| 0.2 | 1 | 30 |
|
||||
| 0.3 | 2 | 80 |
|
||||
| 0.4 | 3 | 110 |
|
301
reclamation-demenagement.org
Normal file
301
reclamation-demenagement.org
Normal file
|
@ -0,0 +1,301 @@
|
|||
#+Title: Réclamation Déménagement
|
||||
|
||||
* Entretien & Devis
|
||||
|
||||
Madame, Monsieur,
|
||||
|
||||
Nous avons confié la réalisation de notre déménagement à votre société. Avant
|
||||
cela nous étions passé par un site internet qui nous permettait d'entrer en
|
||||
contact et de réaliser des devis auprès de différentes sociétés de déménagement.
|
||||
|
||||
Votre société CEDS nous a contacté par le biais de votre commerciale. Après
|
||||
discussion il a été convenu d'un rendez-vous environ 3 semaines avant la date
|
||||
prévue car nous n'étions pas sûr du volume dont nous avions besoin.
|
||||
|
||||
Nous désirions qu'un professionnel constate le travail qu'il y avait à réaliser
|
||||
car nous voulions que notre déménagement se passe du mieux possible sans
|
||||
mauvaise surprise le jour J pour nous comme pour la société concernée.
|
||||
|
||||
Lors de cet entretien nous avions spécifié très clairement que le déménagement
|
||||
devait se dérouler le 22 décembre, ni avant (nous devions signer chez le notaire
|
||||
le 21 décembre) ni après car ma femme avait des impératifs de travail. Nous
|
||||
avions même précisé que si la date n'était pas disponible nous ne désirions pas
|
||||
aller plus loin.
|
||||
|
||||
Toujours lors de cet entretien et avec la commerciale nous avons vérifié à
|
||||
l'aide de google map et google street que les rues n'étaient pas accessibles aux
|
||||
véhicules de plus de 5,5T. Elle nous avait rassuré en nous parlant de deux
|
||||
voyages ou d'un transbordement.
|
||||
|
||||
Nous avions également demandé si la société employait des sous-traitants. Elle
|
||||
nous avait répondu que cela arrivait mais qu'il (la société CEDS) ne travaillait
|
||||
qu'avec des personnes de confiances qu'il connaissait bien. Puis elle avait
|
||||
éludé la suite de la discussion en nous disant qu'il fallait aussi qu'on se
|
||||
fasse mutuellement confiance.
|
||||
|
||||
Après cet entretien, nous avons décidé de signer un contrat avec votre société
|
||||
et puisque je suis à la fois très occupé et que j'ai connu des épisodes de
|
||||
lombalgies aïgues (dont un ayant causé un arrêt de travail) nous avons souscris
|
||||
un contrat catégorie 2 confort afin que je n'ai pas de manutention à effectuer,
|
||||
où votre société s'engage à :
|
||||
|
||||
- mettre des emballages à notre disposition ;
|
||||
- emballer et déballer tous les objets fragiles ;
|
||||
- mettre en penderies les vêtements sur cintres, précisons que nous avions
|
||||
convenu avec la commerciale que 2 penderies étaient nécessaires)
|
||||
- conditionner les matelas et sommiers sous housses et protéger le mobilier ;
|
||||
- démonter et remonter le mobilier non fixé
|
||||
- charger et transporter les bien en camion capitonné
|
||||
- décharger et remettre en place le mobilier.
|
||||
|
||||
De notre coté nous nous sommes engagé à emballer et déballer tous les objets non
|
||||
fragiles. Ajoutons que le déménagement comprenait le transport d'un piano
|
||||
(facturé 150€) ainsi qu'un échange de meuble entre Vitrolle et notre nouvel
|
||||
appartement. Tout celà pour un montant de 2154€.
|
||||
|
||||
* Appel téléphonique et surfacturation
|
||||
|
||||
Huit à neuf jours avant la date fixée j'ai reçu un appel me disant qu'un
|
||||
transbordement allait être nécessaire car la rue d'arrivée ne permettait pas
|
||||
l'accès à un camion plus gros que 5,5T ; et tout cela pour 150€ de plus.
|
||||
Augmentation que mon épouse a refusé de payer dans la mesure où votre société avait
|
||||
connaissance de ces faits le jour où le devis a été établi.
|
||||
|
||||
* Veille: 21 décembre 2016
|
||||
|
||||
Le 21 décembre 2016 à 20h45, nous avons reçu un appel des déménageurs nous
|
||||
demandant de voir l'appartement afin de mieux s'organiser pour le lendemain.
|
||||
Afin de faciliter le déménagement et leur travail nous avons accepté. Ils sont
|
||||
arrivés aux environs de 21h30 et ont fait un rapide tour de l'appartement. En
|
||||
partant ils nous ont précisé qu'ils commenceraient entre 8h et 8h15 le
|
||||
lendemain.
|
||||
|
||||
Nous ne les reverrons plus.
|
||||
|
||||
* Date prévue de déménagement: 22 décembre 2016
|
||||
|
||||
Le jour J tout était prêt. Mon épouse est partie sur le lieu de notre future
|
||||
résidence pour transporter les enfants et nos chats afin qu'ils ne dérangent
|
||||
pas les déménageurs. Après 9h30 un camion (grosse contenance, 19T) et son
|
||||
chauffeur non accompagné des deux déménageurs que nous avions vu la veille s'est
|
||||
présenté.
|
||||
|
||||
À la demande du chauffeur c'est moi qui l'ai aidé à gérer la circulation pour
|
||||
qu'il puisse manœuvrer et entrer dans la résidence. Le chauffeur m'explique
|
||||
qu'il n'arrive pas à joindre les deux autres personnes. Inquiêt j'essaye d'en
|
||||
apprendre plus, il m'explique qu'il a dormi dans le camion et que ses deux
|
||||
collègues sont partis pour aller dormir dans un hôtel la veille et qu'ils ne
|
||||
répondent plus ce matin.
|
||||
|
||||
Il m'explique que pour gagner du temps nous allons commencer à descendre les
|
||||
matelas et les planches. Je dois l'aider à descendre des meubles dans l'assenceur
|
||||
et à transporter les matelas (ce sont des futons difficiles à manipuler).
|
||||
|
||||
Ces descentes partielles de meubles sont entrecoupées par les appels du
|
||||
chauffeur à ses responsables qui lui disent qu'il doit commencer le déménagement
|
||||
et que les deux déménageurs vont finir par arriver.
|
||||
|
||||
Après 10h mon épouse a appelé le standard de CEDS expliquant la situation. Ils
|
||||
nous répondent qu'ils se renseignent et qu'ils nous tiennent au courant. Plus
|
||||
tard, vers 11h sans réponse de leur part, c'est moi qui appelle le standard. Je
|
||||
donne mon nom et j'explique de nouveau la situation. Apparemment mon
|
||||
interlocuteur est au courant. La personne au bout du fil m'explique qu'en effet
|
||||
deux des déménageurs sont accidentés. Je m'inquiète un peu pour eux. Il poursuit
|
||||
en m'expliquant qu'ils ont été accidenté sur un chantier la veille. Chose qui me
|
||||
semble évidemment impossible puisque je les ai vu la veille à 21h30. Lorsque je
|
||||
lui explique celà, il me répond que ce n'était pas eux. Evidemment mon sang n'a
|
||||
fait qu'un tour et quand je hausse le ton, il se met sur la défensive et
|
||||
finalement quand je reprends la parole que je lui explique que je les ai vu la
|
||||
veille à 21h30. Il me dit, et je cite : "Je vous mens par omission". Il poursuit
|
||||
en me disant qu'il va me rappeler rapidement.
|
||||
|
||||
Je refuse que le déménagement continue sans la présence des deux autres intervenants.
|
||||
Mes meubles sont à l'extérieur à moitié protégés.
|
||||
|
||||
Sans retour je fini par rappeler à 12h05, je lui explique que s'il n'est pas
|
||||
capable de trouver une autre solution je préfère tout annuler. Il me répond
|
||||
alors qu'il me rappellera à 12h30 avec et ce sont ses mots "une réponse
|
||||
définitive".
|
||||
|
||||
Entre temps comme il est difficile de joindre la société j'appelle aussi la
|
||||
commerciale elle aussi au courant de la situation.
|
||||
|
||||
Pendant ce temps, le chauffeur m'explique que sa direction lui dit qu'il va
|
||||
trouver deux personnes, qu'elles seront disponibles en début d'après midi et que
|
||||
le chauffeur doit aller les chercher.
|
||||
|
||||
Je m'aperçois vers 13h que le chauffeur à laissé le camion ouvert devant
|
||||
l'entrée de mon immeuble. Mes meubles sont laissés sans surveilances à
|
||||
l'extérieur. Je décide d'attendre jusqu'à 13h30. M'apercevant que la situation
|
||||
va difficilement être gérable en calculant le temps nécéssaire pour finir le
|
||||
déménagement, je décide donc de remonter tous les meubles et les matelas seul
|
||||
dans l'appartement.
|
||||
|
||||
Je ne reverrai plus le chauffeur.
|
||||
|
||||
Vers 15h30, la commerciale nous appelle pour nous proposer une solution pour
|
||||
déménager le lendemain que j'accepte faute de mieux.
|
||||
|
||||
Avec mon épouse nous nous retrouvons donc dans une situation délicate où nous
|
||||
n'avons plus la possibilité de dormir sur place. Nous devons donc déménager
|
||||
nous-même notre literie. Ce que nous faisons avec nos véhicules personnels.
|
||||
|
||||
Le camion est toujours dans la résidence après notre dernier départ ce jour là à
|
||||
17h30. Nous expliquons la situation à la gardienne de notre résidence, seule
|
||||
personne capable de réouvrir la chaîne pour libérer le camion.
|
||||
|
||||
Nous faisons de notre mieux pour déménager et installer notre literie pour
|
||||
dormir dans notre nouvel appartement.
|
||||
|
||||
À 19h le chauffeur du camion nous téléphone. Nous le redirigeons vers la
|
||||
gardienne qui n'était plus présente passé 19h. Il nous recontacte à 19h15 nous
|
||||
demandant de venir lui ouvrir la chaîne et il nous explique que les déménageurs
|
||||
attendus étaient au commissariat.
|
||||
|
||||
Nous refusons de perdre 50 minutes pour lui ouvrir la chaîne et le portail
|
||||
sachant que nous devions nous occuper de nourrir nos enfants et de les installer
|
||||
dans un lieux décent pour la nuit.
|
||||
|
||||
* Deuxième jour: 23 décembre 2016
|
||||
|
||||
** Premier voyage
|
||||
|
||||
Le deuxième jour, les déménageurs viennent de Marseille et arrivent à Saint
|
||||
Laurent vers 12h dans un véhicule de moins de 5,5T. Je suis seul avec eux car
|
||||
mon épouse travaille, (elle est infirmière et travaille de 8h à 20h).
|
||||
|
||||
Le déménagement commence à 12h c'est moi qui démonte ma table pour faire gagner
|
||||
du temps. Un des déménageur s'occupe d'emballer le fragile en faisant celà il
|
||||
utilise un cutter pour couper le papier bulle directement sur notre table de
|
||||
ferme en chêne. Lorsque je m'en aperçois, je lui explique gentillement de mettre
|
||||
un plaque en bois pour protéger. Ce qu'il fait pendant quelques minutes, puis
|
||||
dès que j'ai le dos tourné n'utilise plus la plaque.
|
||||
|
||||
Assez vite les déménageurs décident de réaliser deux voyages. Ils déscendent le
|
||||
piano lors du premier voyage et l'entreprosent dans le camion.
|
||||
|
||||
Je parle des vêtements sur cintres ils m'expliquent qu'ils n'ont qu'un seul
|
||||
carton faisant penderie. Bien insuffisant car ils nous en fallait deux comme
|
||||
précisé lors de l'évaluation des besoins. À charge pour nous de les transporter.
|
||||
|
||||
Je me rends avec eux dans mon sous-sol pour leur montrer ce qui doit être
|
||||
déménagé.
|
||||
|
||||
Un des déménageurs m'explique que pour tout ce qui est fragile, il vaut mieux
|
||||
que je le déménage moi même dans mon véhicule personnel. Je charge donc ma C4
|
||||
Picasso 7 places de beaucoup d'objets fragiles :
|
||||
|
||||
- luminaire
|
||||
- Les verres les plus onéreux de notre service
|
||||
- et objets divers
|
||||
|
||||
Ils se chargent de finir de vider la cave (selon leur dire) et nous partons pour
|
||||
le premier voyage. Juste avant de partir, je leur demande s'ils sont au courant
|
||||
de places de parking réservées devant notre appartement. Lorsque j'explique au
|
||||
responsable que les places étaient réservées la veille mais qu'à ma connaissance
|
||||
elles ne le seront pas aujourd'hui son attitude change du tout au tout.
|
||||
|
||||
Il s'inquiète, il m'explique que s'il avait su, il aurait refusé le contrat.
|
||||
J'apprend alors qu'il y a un contrat. Pendant un moment je me suis fortement
|
||||
inquieté pensant qu'il ne réaliserait pas le second voyage.
|
||||
Il me rassure cependant.
|
||||
|
||||
Nous arrivons à notre nouvel appartement pour vider le camion. Les places sont
|
||||
occupées et beaucoup de temps est perdu pour laisser passer les véhicules.
|
||||
|
||||
Le déménageur m'explique qu'après mon déménagement ils vont en faire un autre à
|
||||
Nice le soir même.
|
||||
|
||||
J'aide les déménageurs en portant moi même ainsi que mon fils de 14 ans des
|
||||
cartons et certains meubles. Ils commencent à mettre les meubles dans la pièce
|
||||
principale, comme je suis seul, j'ai du mal à expliquer la répartition des
|
||||
meubles (il était prévu que ce soit mon épouse qui s'en occupe).
|
||||
|
||||
Certains meubles ont été montés à tort alors qu'ils devaient aller au garage.
|
||||
D'autres ont été entreposés dans la mauvaise pièce. Lorsque j'explique tout celà
|
||||
je sens très bien un agacement, malheureusement, je ne pouvais pas être partout.
|
||||
|
||||
Ils devaient monter le piano, mais ils avaient oublier une planche à roulette.
|
||||
Le piano fera donc un aller-retour.
|
||||
|
||||
** Second voyage
|
||||
|
||||
Pendant le retour mon fils reste dans le nouvel appartement, il essai
|
||||
d'organiser les cartons déposés en vrac, afin de libérer de l'espace pour le
|
||||
deuxième transport.
|
||||
|
||||
Lors du second voyage, il fait nuit, j'aide toujours les déménageurs.
|
||||
|
||||
Juste avant le second départ, dans le salon il reste un amas d'objets divers
|
||||
fragiles que le déménageur me conseille de déménager plus tard dans ma C4.
|
||||
|
||||
Ce que je ne peux pas faire le soir même car elle est déjà remplie par le
|
||||
premier voyage et je n'ai pas eu le temps de la vider. Ils ne m'ont pas aidé à
|
||||
vider mon véhicule.
|
||||
|
||||
Cette fois-ci mon épouse est rentrée il est 20h40, elle découvre que beaucoup de
|
||||
meubles sont mal rangés. Cette fois-ci j'ai la possibilité de rester en bas pour
|
||||
aider les déménageurs à vider le camion et aussi à mieux gérer la répartition
|
||||
entre les meubles qui doivent aller dans le garage et ceux qui doivent aller
|
||||
dans l'appartement.
|
||||
|
||||
Mon épouse demande au responsable de déplacer les meubles dans les bonnes
|
||||
pièces. Ce qu'il fait avec l'aide de notre fils et de mon épouse qui a déjà
|
||||
travaillé 12h.
|
||||
|
||||
Par la suite, le déménageur s'inquiète du déménagement du piano. Les déménageurs
|
||||
nous demandent de les aider à monter l'instrument à l'intérieur de
|
||||
l'appartement. Ce que nous sommes bien obligé de faire. Mon fils est en haut des
|
||||
marches rejoins par deux déménageurs, un troisième est sur le coté, et mon
|
||||
épouse et moi nous poussons vers le haut.
|
||||
|
||||
Les déménageurs partent sans remonter les meubles vers 22h30.
|
||||
|
||||
* Suite du déménagement.
|
||||
|
||||
À ce jour nous ne trouvons pas la visserie très particulière permettant de
|
||||
remonter le bureau de ma fille. J'ai cherché ces vis à Brico Depot, à Castorama
|
||||
en demandant à chaque fois l'avis des responables de rayon. Ils m'ont expliqué
|
||||
que ces vis seraient certainement introuvables dans le commerce rendant notre
|
||||
bureau inutilisable.
|
||||
|
||||
En examinant notre table de ferme, nous voyons qu'il y a beaucoup de coups de
|
||||
cutter visibles.
|
||||
|
||||
Une marche de notre escalier en bois a été abîmée lors de la montée du piano.
|
||||
|
||||
Lors de notre retour au premier appartement, j'ai eu la mauvaise surprise de
|
||||
constater que beaucoup d'objets ont été "oubliés":
|
||||
- une caisse de livre ;
|
||||
- un fer à repassé dans un carton ;
|
||||
- un ventilateur...
|
||||
|
||||
Le tout nécessitant deux aller-retour en C4.
|
||||
|
||||
Autant vous dire que nous ne sommes pas du tout satisfaits du niveau de la
|
||||
prestation qui nous a été fournie et du manque du professionnalisme de vos
|
||||
services.
|
||||
|
||||
J'ai à ce jour une douleur lombaire que je vais avoir du mal à faire passer.
|
||||
|
||||
Il est évident que le volume réel transporté était très en dessous du volume prévu.
|
||||
Nous avons fait de 8 à 10 voyages de C4 de plus que prévu.
|
||||
|
||||
Nous estimons que nous avons subit un grave préjudice. Un stress très important
|
||||
pour notre famille. De l'anxiété et un retard qui s'est répercuté sur les
|
||||
travaux que nous avions à faire sur l'appartement que nous quittons.
|
||||
|
||||
Les prestations fournies n'ont pas été à la hauteur de ce que nous avons payé.
|
||||
|
||||
Nous vous demandons donc un remboursement conséquent du montant que nous avons
|
||||
dû payer. Sans remboursement satisfant d'ici 15 jours, nous transmettrons le
|
||||
dossier à l'inspection du travail, la répression des fraudes, la société des
|
||||
consommateurs ainsi qu'à notre assistance juridique.
|
||||
|
||||
Bien entendu nous pouvons vous fournir des témoignages de notre voisinage, ainsi
|
||||
que d'autres preuves de tout ce que nous avançons.
|
||||
|
||||
Dans l'attente d'une réponse,
|
||||
|
||||
le 28 décembre 2016,
|
||||
Yann Esposito.
|
Loading…
Reference in a new issue