--- title: "Nouveau type et lentilles dans Haskell" date: "2016-08-20" output: html_document --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) ``` ## 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.
> -- ##~~ define a new type ~~## --
>
> -- #~ a new type with unnamed fields ~# --
> data Point = Point Float Float deriving (Show)
> -- variable of type Point:
> let point = (Point 3 4)
> point
Point 3.0 4.0
> :t point
point :: Point
> -- 'Point' is like a function:
> :t Point
Point :: Float -> Float -> Point
> -- function acting on 'Point' variables:
> :{
> let squareNorm :: Point -> Float
> squareNorm (Point x y) = x^2+y^2
> :}
> squareNorm point
25.0
>
> -- #~ a new type with named fields ~# --
> data NamedPoint = NamedPoint{ _x :: Float, _y :: Float } deriving (Show)
> let point = (NamedPoint 3 4)
> point
NamedPoint {_x = 3.0, _y = 4.0}
> -- we can set the field values by their name:
> NamedPoint { _y = 0, _x = 1 }
NamedPoint {_x = 1.0, _y = 0.0}
> -- get the value of a field:
> _x point
3.0
> -- update a field (this creates a new 'NamedPoint'):
> point { _y = 5 }
NamedPoint {_x = 3.0, _y = 5.0}
>
> -- #~ field with unpredefined type ~# --
> data Foo a = Foo{ _bar :: Int, _baz :: a } deriving (Show)
> let w = Foo { _bar = 1, _baz = "hello" }
> w
Foo {_bar = 1, _baz = "hello"}
> :t w
w :: Foo [Char]
> -- function acting on 'Foo String':
> :{
> let reverseBaz :: Foo String -> Foo String
> reverseBaz (Foo bar baz) = Foo { _bar = bar, _baz = reverse baz }
> :}
> reverseBaz w
Foo {_bar = 1, _baz = "olleh"}
Foo { _bar = bar, _baz = reverse baz }
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 :
> -- #~ lens example: _2 ~# --
> import Control.Lens
> let a = ("hello", 0::Int)
> -- to get the 2nd element:
> view _2 a
0
> -- more concisely:
> a ^. _2
0
> -- to set a value to the 2nd element:
> set _2 "world" a
("hello","world")
> -- more concisely:
> _2 .~ "world" $ a
("hello","world")
> -- to apply a function
> over _2 (+1) $ a
("hello",1)
-- NewType_makeLenses.hs
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens hiding (element)
data Foo a = Foo{ _bar :: Int, _baz :: a } deriving (Show)
makeLenses ''Foo
> -- load the file creating the lenses
> :l NewType_makeLenses
>
> -- the makeLenses function has defined new lenses:
> :i bar
bar :: Lens' (Foo a0) Int -- Defined at NewType_makeLenses.hs:8:1
> :i baz
baz :: Lens (Foo a0) (Foo a1) a0 a1 -- Defined at NewType_makeLenses.hs:8:1
>
> -- they can be used as any other lens:
> let x = Foo { _bar = 1, _baz = "hello" }
> x ^. bar
1
> baz .~ "hi" $ x
Foo {_bar = 1, _baz = "hi"}
>
> -- the 'reverseBaz' function again:
> :{
> let reverseBaz :: Foo String -> Foo String
> reverseBaz = over baz reverse
> :}
> reverseBaz x
Foo {_bar = 1, _baz = "olleh"}