-
Nouveau type et lentilles dans Haskell
2016-08-20
SourceDé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"}
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 typeFoo String
(= Foo [Char]
). Elle n’agit pas sur le premier champ, et cependant avons dû recopier ce champ :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)
Créer ses lentilles
On crée des lentilles de la façon suivante :
-- NewType_makeLenses.hs {-# LANGUAGE TemplateHaskell #-} import Control.Lens hiding (element) data Foo a = Foo{ _bar :: Int, _baz :: a } deriving (Show) makeLenses ''Foo
Cela crée deux nouvelles lentilles,
bar
etbaz
qui, respectivement, se concentrent sur le champbar
et le champbaz
. On les utilise alors comme la lentille_2
.> -- 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"}
Ainsi, la fonction
reverseBaz
a pu être définie de façon bien plus commode.