Recherche…


Nombres naturels en algèbre de type

Nous pouvons établir un lien entre les types Haskell et les nombres naturels. Cette connexion peut être faite en attribuant à chaque type le nombre d'habitants qu'il possède.

Types d'union finis

Pour les types finis, il suffit de voir que l'on peut attribuer un type naturel à chaque nombre, en fonction du nombre de constructeurs. Par exemple:

type Color = Red | Yellow | Green

serait 3 . Et le type Bool serait 2 .

type Bool = True | False

Unicité jusqu'à l'isomorphisme

Nous avons vu que plusieurs types correspondraient à un seul numéro, mais dans ce cas, ils seraient isomorphes. C'est-à-dire qu'il y aurait une paire de morphismes f et g , dont la composition serait l'identité, reliant les deux types.

f :: a -> b
g :: b -> a

f . g == id == g . f

Dans ce cas, nous dirions que les types sont isomorphes . Nous considérerons deux types égaux dans notre algèbre tant qu'ils sont isomorphes.

Par exemple, deux représentations différentes du nombre deux sont trivialement isomorphes:

type Bit  = I    | O
type Bool = True | False

bitValue :: Bit -> Bool
bitValue I = True
bitValue O = False

booleanBit :: Bool -> Bit
booleanBit True  = I
booleanBit False = O

Parce que nous pouvons voir bitValue . booleanBit == id == booleanBit . bitValue

Un et zéro

La représentation du nombre 1 est évidemment un type avec un seul constructeur. Dans Haskell, ce type est canoniquement le type () , appelé Unit. Tout autre type avec un seul constructeur est isomorphe à () .

Et notre représentation de 0 sera un type sans constructeurs. C'est le type Void dans Haskell, tel que défini dans Data.Void . Cela équivaudrait à un type non habité, sans constructeurs de données:

data Void

Ajout et multiplication

L'addition et la multiplication ont des équivalents dans ce type algèbre. Ils correspondent aux unions marquées et aux types de produits .

data Sum a b = A a | B b
data Prod a b = Prod a b

Nous pouvons voir comment le nombre d'habitants de chaque type correspond aux opérations de l'algèbre.

De manière équivalente, nous pouvons utiliser Either et (,) comme constructeurs de type pour l'addition et la multiplication. Ils sont isomorphes à nos types précédemment définis:

type Sum' a b = Either a b
type Prod' a b = (a,b)

Les résultats attendus de l'addition et de la multiplication sont suivis par le type algèbre jusqu'à l'isomorphisme. Par exemple, on peut voir un isomorphisme entre 1 + 2, 2 + 1 et 3; comme 1 + 2 = 3 = 2 + 1.

data Color = Red | Green | Blue

f :: Sum () Bool -> Color
f (Left ())     = Red
f (Right True)  = Green
f (Right False) = Blue

g :: Color -> Sum () Bool
g Red   = Left ()
g Green = Right True
g Blue  = Right False

f' :: Sum Bool () -> Color
f' (Right ())   = Red
f' (Left True)  = Green
f' (Left False) = Blue

g' :: Color -> Sum Bool ()
g' Red   = Right ()
g' Green = Left True
g' Blue  = Left False

Règles d'addition et de multiplication

Les règles communes de commutativité, associativité et distributivité sont valables car il existe des isomorphismes triviaux entre les types suivants:

-- Commutativity
Sum a b           <=> Sum b a
Prod a b          <=> Prod b a
-- Associativity
Sum (Sum a b) c   <=> Sum a (Sum b c)
Prod (Prod a b) c <=> Prod a (Prod b c)
-- Distributivity
Prod a (Sum b c)  <=> Sum (Prod a b) (Prod a c)

Types récursifs

Des listes

Les listes peuvent être définies comme suit:

data List a = Nil | Cons a (List a) 

Si nous traduisons cela dans notre algèbre de type, nous obtenons

Liste (a) = 1 + a * Liste (a)

Mais nous pouvons maintenant substituer plusieurs fois List (a) dans cette expression pour obtenir:

Liste (a) = 1 + a + a * a + a * a * a + a * a * a * a + ...

Cela a du sens si nous voyons une liste comme un type qui ne peut contenir qu’une valeur, comme dans [] ; ou toute valeur de type a , comme dans [x] ; ou deux valeurs de type a , comme dans [x,y] ; etc. La définition théorique de List que nous devrions en tirer serait:

-- Not working Haskell code!
data List a = Nil
            | One a
            | Two a a
            | Three a a a 
            ...

Des arbres

Nous pouvons faire la même chose avec les arbres binaires, par exemple. Si nous les définissons comme:

data Tree a = Empty | Node a (Tree a) (Tree a)

Nous obtenons l'expression:

Arbre (a) = 1 + a * Arbre (a) * Arbre (a)

Et si nous faisons les mêmes substitutions encore et encore, nous obtiendrons la séquence suivante:

Arbre (a) = 1 + a + 2 (a * a) + 5 (a * a * a) + 14 (a * a * a * a) + ...

Les coefficients que nous obtenons ici correspondent à la séquence des nombres catalans, et le nième nombre catalan est précisément le nombre d'arbres binaires possibles avec n nœuds.

Dérivés

La dérivée d'un type est le type de son type de contexte à un trou. C'est le type que nous obtiendrions si nous faisons disparaître une variable de type à chaque point et additionnons les résultats.

Par exemple, nous pouvons prendre le triple type (a,a,a) et le dériver, en obtenant

data OneHoleContextsOfTriple = (a,a,()) | (a,(),a) | ((),a,a)

Ceci est cohérent avec notre définition habituelle de la dérivation, comme suit:

d / da (a * a * a) = 3 * a * a

Plus d'informations sur ce sujet peuvent être lues sur cet article .

Les fonctions

Les fonctions peuvent être considérées comme exponentielles dans notre algèbre. Comme nous pouvons le voir, si nous prenons un type a avec n instances et un type b avec m instances, le type a -> b aura m a -> b la puissance de n instances.

Par exemple, Bool -> Bool est isomorphe à (Bool,Bool) , comme 2 * 2 = 2².

iso1 :: (Bool -> Bool) -> (Bool,Bool)
iso1 f = (f True,f False)

iso2 :: (Bool,Bool) -> (Bool -> Bool)
iso2 (x,y) = (\p -> if p then x else y)


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow