2015-08-22 13 views
8

Ich versuche, eine Instanz von Enum für die folgende Art zu schreiben:Haskell Aufzählung

-- Type declarations: 

-- Octave 
data Octave = 
    O1 | O2 | O3 
    deriving (Show, Read, Eq, Ord, Bounded, Enum) 

-- Note 
data Note = 
    A | B | C | D | E | F 
    deriving (Show, Read, Eq, Ord, Bounded, Enum) 

-- Pitch 
data Pitch = Pitch Octave Note 
    deriving (Show, Eq, Ord) 

-- Why doesn't this work? 
instance Enum Pitch where 
    fromEnum (Pitch o n) = (fromEnum o)*6 + (fromEnum n) 
    toEnum x = (Pitch o n) 
     where 
      o = toEnum (x `div` 6) 
      n = toEnum (x `mod` 6) 

Dies funktioniert gut für:

[(Pitch O1 A) .. (Pitch O3 F)] 

Aber nicht für:

[(Pitch O1 A) .. ] 

Mit der Fehler:

*** Exception: toEnum{Octave}: tag (3) is outside of enumeration's range (0,2) 

Ich verstehe den Fehler. Meine Fragen sind: Wie schreibt man die Enum-Instanz korrekt, um diese Aufzählung durchzuführen? Ist es möglich? Am allermeisten: Ist es eine gute Praxis?

Antwort

9

Ihr Problem ist, dass Pitch ist implizit begrenzt - d. H. Es hat ein geringstes und größtes Element - aber Sie spiegeln diese Beschränktheit in Ihrem Code nicht wider. Die Code

[Pitch O1 A ..] 

desugars zu

enumFrom (Pitch O1 A) 

die succ ing die generierten Werte, bis sie succ s Pitch O3 F und sprengt hält. Wie konnte es wissen, dass es dort aufhören sollte?

From the Prelude documentation:

For any type that is an instance of class Bounded as well as Enum , the following should hold:

  • enumFrom and enumFromThen should be defined with an implicit bound, thus:

    enumFrom  x = enumFromTo  x maxBound 
    enumFromThen x y = enumFromThenTo x y bound 
        where 
        bound | fromEnum y >= fromEnum x = maxBound 
          | otherwise    = minBound 
    

Damit dieses Problem zu beheben, fügen Sie einfach

instance Bounded Pitch where 
    minBound = Pitch minBound minBound 
    maxBound = Pitch maxBound maxBound 

und dann den Code aus der Dokumentation hinzufügen:

instance Enum Pitch where 
    -- ... 
    enumFrom  x = enumFromTo  x maxBound 
    enumFromThen x y = enumFromThenTo x y bound 
    where 
     bound | fromEnum y >= fromEnum x = maxBound 
      | otherwise    = minBound 

und jetzt [Pitch O1 A ..] stoppt am Ende:

λ> [Pitch O1 A ..] 
[Pitch O1 A,Pitch O1 B,Pitch O1 C,Pitch O1 D,Pitch O1 E,Pitch O1 F,Pitch O2 A,Pitch O2 B,Pitch O2 C,Pitch O2 D,Pitch O2 E,Pitch O2 F,Pitch O3 A,Pitch O3 B,Pitch O3 C,Pitch O3 D,Pitch O3 E,Pitch O3 F] 

Randbemerkung: x `divMod` y == (x `div` y, x `mod` y): Sie separate Anrufe div und mod mit Pattern-Matching auf einem einzigen Aufruf divMod ersetzen können. (Für streng positive Zahlen, wie diese, ich glaube, ich habe gehört, dass quotRem eine bessere Wahl sein kann;. quot und rem sind wie div und mod, aber mit unterschiedlichen Vorzeichen bezogenen Verhaltensweisen) Darüber hinaus können Sie Ihre 6 s ersetzen 1 + (fromEnum (maxBound :: Note)) zu vermeiden, versehentlich die Nummer falsch zu bekommen.

Verwandte Themen