2011-02-12 6 views
10

J'essayais de mettre en œuvre une machine d'état dans Haskell. Une version simplifiée est la suivante:Modèle de machine d'état dans Haskell: erreur de type infinie

Dans n'importe quel état, vous pouvez alimenter la machine en entier et vous obtenez un nombre entier. Dans l'état A, la machine double ses entrées. Dans l'état B, la machine vous restitue simplement votre entrée. Chaque fois que vous voyez un zéro dans l'un ou l'autre état, passez à l'autre état. Sinon, l'état ne change pas.

Voici la méthode mon: il suffit que chaque état soit une fonction qui renvoie à la fois sa sortie et la fonction correspondant à l'autre état.

module Main where 

a x | x == 0 = (0,b) 
a x = (2*x, a) 

b x | x == 0 = (0,a) 
b x = (x, b) 

evalstate s [] = [] 
evalstate s (x:xs) = (v:evalstate s' xs) 
    where (v,s') = s x 

main :: IO() 
main = putStrLn $ show $ evalstate a [1,1,2,3,4,0,2,3,3] 

Malheureusement, les types de a et b sont infinies et se plaint GHC:

Occurs check: cannot construct the infinite type: t = t1 -> (t2, t)

Quelle est la façon d'exprimer ce modèle dans Haskell?

je pourrais bien sûr faire quelque chose comme:

s 'a' x | x == 0 = (0,'b') 

en utilisant des codes de caractères pour l'état, mais le modèle de fonction semble plus élégante.

+0

tâche pour la lecteur: Rendez ce modèle plus commun et créez une interface monadique pour celui-ci. – fuz

+2

Sur un point de style: il est généralement plus clair si vous dites 'a 0 = (0, b)' au lieu de 'a x | x == 0 = (0, b) '. – dave4420

+0

Au lieu d'un code de caractère, je suggère d'utiliser un type de données, comme 'data State = A | B'. mais le premier est sympa aussi. – fuz

Répondre

16

Vous essayez de définir une machine d'état avec le type

type SM = Int -> (Int, SM) 

mais Haskell ne permet pas. Vous devez utiliser data ou newtype d'introduire un nouveau type nommé:

newtype SM = SM (Int -> (Int, SM)) 

est en dessous de votre programme avec ce changement mineur appliqué, de sorte qu'il compile et se comporte maintenant comme prévu:

module Main where 

newtype SM = SM (Int -> (Int, SM)) 

a = SM a' 
    where 
    a' x | x == 0 = (0, b) 
    a' x = (2 * x, a) 

b = SM b' 
    where 
    b' x | x == 0 = (0, a) 
    b' x = (x, b) 

evalstate s [] = [] 
evalstate (SM s) (x : xs) = (v:evalstate s' xs) 
    where (v, s') = s x 

main :: IO() 
main = putStrLn $ show $ evalstate a [1,1,2,3,4,0,2,3,3] 
+0

Merci. J'utilise ceci dans mon code maintenant. – luispedro

+3

Plus précisément, la raison pour laquelle Haskell n'autorise pas 'type SM' est parce qu'il est récursif. L'inférence de type pour les types équi-récursifs est indécidable (je pense), donc vous devez introduire un constructeur au niveau de la valeur pour aider le typechecker à voir ce qui se passe. – luqui

+11

L'inférence de type équirécursif est plus complexe mais peut être décidée, mais en permettant d'inférer des types équirécursifs, de nombreuses erreurs de programmation courantes deviennent des programmes typables. C'est ennuyeux car cela tend à retarder le moment où les erreurs du programmeur sont attrapées à partir de l'endroit où les fonctions sont définies et où elles sont utilisées. –