---
title: "Nouveau type et lentilles dans Haskell"
date: "2016-08-20"
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

<style type="text/css">
.hs-keyglyph, .hs-layout {
	color: #ff9358; 
	font-weight:bold
}
.hs-keyword {
	color: #00FF00; 
	font-weight: bold
} 
.hs-comment, .hs-comment a {
	color: #0066ff; 
	font-weight: bold; 
	font-style: italic
}
.hs-str, .hs-chr {
	color: #ff2200;
}
.hs-num {
	color: #f0dfaf}
.output {
	font-style: italic; 
	color: gray
}
.prompt {
	color: orange; 
	font-weight:bold
}
.hs-conid {
	color: yellow
}
.hs-conop {
	color: seaShell; 
	font-weight:bold
}
.hs-varid, .hs-varop, .hs-cpp, .hs-sel, .hs-definition {
  color: #efef8f;
}
pre.scriptHaskell, code.scriptHaskell, ol.linenums li {
	background-color: #303030;
}
pre.scriptHaskell, code.scriptHaskell {
	color: #efef8f;
	display: block;
	padding: 5px;
	word-break: break all;
	word-wrap: break-word;
}
pre.scriptHaskell {
	word-break: break all;
	word-wrap: break-word;
}
code.scriptHaskell {
  white-space: pre;
	font-family: monospace;
	border-radius: 0px;
}
div.sourceCode { 
	overflow-x: auto;
	background-color: #303030;
}
pre.prettyprint.linenums {
  background-color: #303029; 
  border: 1px solid #303030;
	border-radius: 13px;
}
ol.linenums {
  color: #ffdb58
}
</style>


## Définir un nouveau type dans Haskell

On utilise `data` pour définir un nouveau type dans Haskell. Quelques exemples élémentaires sont donnés ci-dessous.

<div class='sourceCode'><pre class="scriptHaskell"><code class="scriptHaskell"><span class='prompt'>></span> <span class='hs-comment'>-- ##~~ define a new type ~~## --</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- #~ a new type with unnamed fields ~# -- </span>
<span class='prompt'>></span> <span class='hs-keyword'>data</span> <span class='hs-conid'>Point</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Point</span> <span class='hs-conid'>Float</span> <span class='hs-conid'>Float</span> <span class='hs-keyword'>deriving</span> <span class='hs-layout'>(</span><span class='hs-conid'>Show</span><span class='hs-layout'>)</span>
<span class='prompt'>></span> <span class='hs-comment'>-- variable of type Point:</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>point</span> <span class='hs-keyglyph'>=</span> <span class='hs-layout'>(</span><span class='hs-conid'>Point</span> <span class='hs-num'>3</span> <span class='hs-num'>4</span><span class='hs-layout'>)</span>
<span class='prompt'>></span> <span class='hs-definition'>point</span>
<span class='output'>Point 3.0 4.0</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>t</span> <span class='hs-varid'>point</span>
<span class='output'>point :: Point</span>
<span class='prompt'>></span> <span class='hs-comment'>-- 'Point' is like a function:</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>t</span> <span class='hs-conid'>Point</span>
<span class='output'>Point :: Float -> Float -> Point</span>
<span class='prompt'>></span> <span class='hs-comment'>-- function acting on 'Point' variables:</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>{</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>squareNorm</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Point</span> <span class='hs-keyglyph'>-&gt;</span> <span class='hs-conid'>Float</span>
<span class='prompt'>></span>     <span class='hs-varid'>squareNorm</span> <span class='hs-layout'>(</span><span class='hs-conid'>Point</span> <span class='hs-varid'>x</span> <span class='hs-varid'>y</span><span class='hs-layout'>)</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>x</span><span class='hs-varop'>^</span><span class='hs-num'>2</span><span class='hs-varop'>+</span><span class='hs-varid'>y</span><span class='hs-varop'>^</span><span class='hs-num'>2</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-definition'>squareNorm</span> <span class='hs-varid'>point</span>
<span class='output'>25.0</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- #~ a new type with named fields ~# -- </span>
<span class='prompt'>></span> <span class='hs-keyword'>data</span> <span class='hs-conid'>NamedPoint</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>NamedPoint</span><span class='hs-layout'>{</span> <span class='hs-sel'>_x</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Float</span><span class='hs-layout'>,</span> <span class='hs-sel'>_y</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Float</span> <span class='hs-layout'>}</span> <span class='hs-keyword'>deriving</span> <span class='hs-layout'>(</span><span class='hs-conid'>Show</span><span class='hs-layout'>)</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>point</span> <span class='hs-keyglyph'>=</span> <span class='hs-layout'>(</span><span class='hs-conid'>NamedPoint</span> <span class='hs-num'>3</span> <span class='hs-num'>4</span><span class='hs-layout'>)</span> 
<span class='prompt'>></span> <span class='hs-definition'>point</span>
<span class='output'>NamedPoint {_x = 3.0, _y = 4.0}</span>
<span class='prompt'>></span> <span class='hs-comment'>-- we can set the field values by their name:</span>
<span class='prompt'>></span> <span class='hs-conid'>NamedPoint</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_y</span> <span class='hs-keyglyph'>=</span> <span class='hs-num'>0</span><span class='hs-layout'>,</span> <span class='hs-sel'>_x</span> <span class='hs-keyglyph'>=</span> <span class='hs-num'>1</span> <span class='hs-layout'>}</span>
<span class='output'>NamedPoint {_x = 1.0, _y = 0.0}</span>
<span class='prompt'>></span> <span class='hs-comment'>-- get the value of a field:</span>
<span class='prompt'>></span> <span class='hs-sel'>_x</span> <span class='hs-varid'>point</span> 
<span class='output'>3.0</span>
<span class='prompt'>></span> <span class='hs-comment'>-- update a field (this creates a new 'NamedPoint'):</span>
<span class='prompt'>></span> <span class='hs-definition'>point</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_y</span> <span class='hs-keyglyph'>=</span> <span class='hs-num'>5</span> <span class='hs-layout'>}</span>
<span class='output'>NamedPoint {_x = 3.0, _y = 5.0}</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- #~ field with unpredefined type ~# --</span>
<span class='prompt'>></span> <span class='hs-keyword'>data</span> <span class='hs-conid'>Foo</span> <span class='hs-varid'>a</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Foo</span><span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Int</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>::</span> <span class='hs-varid'>a</span> <span class='hs-layout'>}</span> <span class='hs-keyword'>deriving</span> <span class='hs-layout'>(</span><span class='hs-conid'>Show</span><span class='hs-layout'>)</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>w</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Foo</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>=</span> <span class='hs-num'>1</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>=</span> <span class='hs-str'>"hello"</span> <span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-definition'>w</span>
<span class='output'>Foo {_bar = 1, _baz = "hello"}</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>t</span> <span class='hs-varid'>w</span>
<span class='output'>w :: Foo [Char]</span>
<span class='prompt'>></span> <span class='hs-comment'>-- function acting on 'Foo String':</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>{</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>reverseBaz</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Foo</span> <span class='hs-conid'>String</span> <span class='hs-keyglyph'>-&gt;</span> <span class='hs-conid'>Foo</span> <span class='hs-conid'>String</span>
<span class='prompt'>></span>     <span class='hs-varid'>reverseBaz</span> <span class='hs-layout'>(</span><span class='hs-conid'>Foo</span> <span class='hs-varid'>bar</span> <span class='hs-varid'>baz</span><span class='hs-layout'>)</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Foo</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>bar</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>reverse</span> <span class='hs-varid'>baz</span> <span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-definition'>reverseBaz</span> <span class='hs-varid'>w</span>
<span class='output'>Foo {_bar = 1, _baz = "olleh"}</span>
</code></pre></div>

La fonction `reverseBaz` définie dans le code ci-dessus a pour action de renverser la chaîne de caractères contenue dans le deuxième champ d'un objet de type `Foo String` (`= Foo [Char]`). Elle n'agit pas sur le premier champ, et cependant avons dû recopier ce champ :

<code class="scriptHaskell">
<span class='hs-conid'>Foo</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>bar</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>reverse</span> <span class='hs-varid'>baz</span> <span class='hs-layout'>}</span>
</code>

Dans ce cas où la classe `Foo` ne contient que deux champs, ce n'est pas très dérangeant. Mais ce serait plus problématique s'il y avait plus de champs. Une méthode permettant d'agir sur un champ sans avoir besoin de recopier les autres est souhaitable.


## Lentilles 

Pour cela, on utilise des *lentilles*, de la librairie `lens`. Ci-dessous, des exemples de la lentille `_2`, la lentille qui se concentre sur la 2ème composante d'une paire : 

<div class='sourceCode'><pre class='scriptHaskell'><code class='scriptHaskell'><span class='prompt'>></span> <span class='hs-comment'>-- #~ lens example: _2 ~# --</span>
<span class='prompt'>></span> <span class='hs-keyword'>import</span> <span class='hs-conid'>Control</span><span class='hs-varop'>.</span><span class='hs-conid'>Lens</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>a</span> <span class='hs-keyglyph'>=</span> <span class='hs-layout'>(</span><span class='hs-str'>"hello"</span><span class='hs-layout'>,</span> <span class='hs-num'>0</span><span class='hs-keyglyph'>::</span><span class='hs-conid'>Int</span><span class='hs-layout'>)</span>
<span class='prompt'>></span> <span class='hs-comment'>-- to get the 2nd element:</span>
<span class='prompt'>></span> <span class='hs-definition'>view</span> <span class='hs-sel'>_2</span> <span class='hs-varid'>a</span> 
<span class='output'>0</span>
<span class='prompt'>></span> <span class='hs-comment'>-- more concisely:</span>
<span class='prompt'>></span> <span class='hs-definition'>a</span> <span class='hs-varop'>^.</span> <span class='hs-sel'>_2</span>
<span class='output'>0</span>
<span class='prompt'>></span> <span class='hs-comment'>-- to set a value to the 2nd element:</span>
<span class='prompt'>></span> <span class='hs-definition'>set</span> <span class='hs-sel'>_2</span> <span class='hs-str'>"world"</span> <span class='hs-varid'>a</span>
<span class='output'>("hello","world")</span>
<span class='prompt'>></span> <span class='hs-comment'>-- more concisely:</span>
<span class='prompt'>></span> <span class='hs-sel'>_2</span> <span class='hs-varop'>.~</span> <span class='hs-str'>"world"</span> <span class='hs-varop'>$</span> <span class='hs-varid'>a</span>
<span class='output'>("hello","world")</span>
<span class='prompt'>></span> <span class='hs-comment'>-- to apply a function </span>
<span class='prompt'>></span> <span class='hs-definition'>over</span> <span class='hs-sel'>_2</span> <span class='hs-layout'>(</span><span class='hs-varop'>+</span><span class='hs-num'>1</span><span class='hs-layout'>)</span> <span class='hs-varop'>$</span> <span class='hs-varid'>a</span>
<span class='output'>("hello",1)</span>
</code></pre></div>


## Créer ses lentilles

On crée des lentilles de la façon suivante :

<div class='sourceCode'><pre class="scriptHaskell"><code class="scriptHaskell"><span class='hs-comment'>-- NewType_makeLenses.hs</span>
<span class='hs-comment'>{-# LANGUAGE TemplateHaskell #-}</span>
<span class='hs-keyword'>import</span> <span class='hs-conid'>Control</span><span class='hs-varop'>.</span><span class='hs-conid'>Lens</span> <span class='hs-varid'>hiding</span> <span class='hs-layout'>(</span><span class='hs-varid'>element</span><span class='hs-layout'>)</span>
<span class='hs-keyword'>data</span> <span class='hs-conid'>Foo</span> <span class='hs-varid'>a</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Foo</span><span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Int</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>::</span> <span class='hs-varid'>a</span> <span class='hs-layout'>}</span> <span class='hs-keyword'>deriving</span> <span class='hs-layout'>(</span><span class='hs-conid'>Show</span><span class='hs-layout'>)</span>
<span class='hs-definition'>makeLenses</span> <span class='hs-chr'>'</span><span class='hs-chr'>'</span><span class='hs-conid'>Foo</span>
</code></pre></div>


Cela crée deux nouvelles lentilles, `bar` et `baz` qui, respectivement, se concentrent sur le champ `bar` et le champ `baz`. On les utilise alors comme la lentille `_2`. 

<div class='sourceCode'><pre class='scriptHaskell'><code class='scriptHaskell'><span class='prompt'>></span> <span class='hs-comment'>-- load the file creating the lenses</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>l</span> <span class='hs-conid'>NewType_makeLenses</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- the makeLenses function has defined new lenses:</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>i</span> <span class='hs-varid'>bar</span>
<span class='output'>bar :: Lens' (Foo a0) Int 	-- Defined at NewType_makeLenses.hs:8:1</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-varid'>i</span> <span class='hs-varid'>baz</span>
<span class='output'>baz :: Lens (Foo a0) (Foo a1) a0 a1 	-- Defined at NewType_makeLenses.hs:8:1</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- they can be used as any other lens:</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>x</span> <span class='hs-keyglyph'>=</span> <span class='hs-conid'>Foo</span> <span class='hs-layout'>{</span> <span class='hs-sel'>_bar</span> <span class='hs-keyglyph'>=</span> <span class='hs-num'>1</span><span class='hs-layout'>,</span> <span class='hs-sel'>_baz</span> <span class='hs-keyglyph'>=</span> <span class='hs-str'>"hello"</span> <span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-definition'>x</span> <span class='hs-varop'>^.</span> <span class='hs-varid'>bar</span>
<span class='output'>1</span>
<span class='prompt'>></span> <span class='hs-definition'>baz</span> <span class='hs-varop'>.~</span> <span class='hs-str'>"hi"</span> <span class='hs-varop'>$</span> <span class='hs-varid'>x</span> 
<span class='output'>Foo {_bar = 1, _baz = "hi"}</span>
<span class='prompt'>></span> 
<span class='prompt'>></span> <span class='hs-comment'>-- the 'reverseBaz' function again: </span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>{</span>
<span class='prompt'>></span> <span class='hs-keyword'>let</span> <span class='hs-varid'>reverseBaz</span> <span class='hs-keyglyph'>::</span> <span class='hs-conid'>Foo</span> <span class='hs-conid'>String</span> <span class='hs-keyglyph'>-&gt;</span> <span class='hs-conid'>Foo</span> <span class='hs-conid'>String</span>
<span class='prompt'>></span>     <span class='hs-varid'>reverseBaz</span> <span class='hs-keyglyph'>=</span> <span class='hs-varid'>over</span> <span class='hs-varid'>baz</span> <span class='hs-varid'>reverse</span>
<span class='prompt'>></span> <span class='hs-conop'>:</span><span class='hs-layout'>}</span>
<span class='prompt'>></span> <span class='hs-definition'>reverseBaz</span> <span class='hs-varid'>x</span>
<span class='output'>Foo {_bar = 1, _baz = "olleh"}</span>
</code></pre></div>

Ainsi, la fonction `reverseBaz` a pu être définie de façon bien plus commode.


## De la lecture sur les lentilles

- https://hackage.haskell.org/package/lens-tutorial-1.0.1/docs/Control-Lens-Tutorial.html

- https://hackage.haskell.org/package/lens

- https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial

- http://lens.github.io/