I am an OOP programmer by trade (Java) who is extremely new to Haskell and the world of purely functional languages. To get my feet wet, I decided to try and write a little musical interval translator/calculator. I am immediately impressed with this language. The power of this allowed me to quickly define an algebra called PitchClass and define a default Isomorphism between many Enumerated types.
With this code I am able to write things like C +: P5
and get G
(the perfect fifth of C) as an output in the GHCi REPL.
I can write
λ: cycleOfFifths = cycle $ map (iso . (P5*:)) [3 .. 12] :: [NoteName]
λ: take 5 cycleOfFifths
And it will output 5 notes from the circle of fifths [Gb,Db,Ab,Eb,Bb]
(which, by the way, are all root notes in the chord progression of Dave Brubeck's jazz standard named... you guessed it... take 5
:D)
class (Enum a) => PitchClass a where
iso :: (PitchClass b) => a -> b
iso = toEnum . fromIntegral . abs . (flip mod 12) . fromIntegral . fromEnum
fiso :: (PitchClass b) => (b -> b) -> a -> a
fiso f = iso . f . iso
(+:) :: (PitchClass b) => a -> b -> a
(+:) x = iso . fiso ((iso x :: Int)+)
(*:) :: (PitchClass b) => a -> b -> a
(*:) x = iso . fiso ((iso x :: Int)*)
(-:) :: (PitchClass b) => a -> b -> a
(-:) x = iso . fiso ((iso x :: Int)-)
instance PitchClass Integer
instance PitchClass Int
data NoteName = A | Bb | B | C | Db | D | Eb | E | F | Gb | G | Ab deriving (Show, Eq, Enum, Bounded);
instance PitchClass NoteName
data Solfege = Do | Di | Re | Ri | Mi | Fa | Fi | Sol | Si | La | Li | Ti deriving (Show, Eq, Enum, Bounded);
instance PitchClass Solfege
data Interval = R | Mi2 | Ma2 | Mi3 | Ma3 | P4 | TT | P5 | Mi6 | Ma6 | Mi7 | Ma7 deriving (Eq, Enum, Show, Bounded);
instance PitchClass Interval
I am a beginner in Haskell, and I was floored by how elegant this was. So I decided to take it further. I wanted to treat a PitchClass as an actual mathematical ring, and have Haskell treat it as such. From reading the doc, it seems that the Num type class is what I need to accomplish this, so I added the following:
instance Num Solfege where
(+) = (+:)
(*) = (*:)
abs = fiso (abs :: Integer -> Integer)
signum = fiso (signum :: Integer -> Integer)
fromInteger = iso
negate = fiso (negate :: Integer -> Integer)
instance Num Interval where
(+) = (+:)
(*) = (*:)
abs = fiso (abs :: Integer -> Integer)
signum = fiso (signum :: Integer -> Integer)
fromInteger = iso
negate = fiso (negate :: Integer -> Integer)
instance Num NoteName where
(+) = (+:)
(*) = (*:)
abs = fiso (abs :: Integer -> Integer)
signum = fiso (signum :: Integer -> Integer)
fromInteger = iso
negate = fiso (negate :: Integer -> Integer)
I am looking for any feedback on either of these two parts. Is there anything that can be made more elegant, or more idiomatic for Haskell programmers?
Here are some things that have been bugging me, but keep in mind I am open to any advice to make my code cleaner, easier to read, and more idiomatic (naming conventions, indention, etc.):
The Num instancing works, but this is far less elegant than my PitchClass, and there are multiple things that I don't like about it. First of all, I repeated the same code three times. Is there a way that I can avoid this duplication?
Secondly, in this case, it would make sense to instance Num on the PitchClass, as in the type of (+) should be something like
Num PitchClass
so that I can writeC + P5
. I don't think the Type System allows this, as PitchClass is a type class, not a type (this took me a lot of fiddling and research for my feeble OOP brain to grasp). However, I still can't shake the feeling that there is some design pattern that would allow this. If there isn't, I am assuming its bad from the perspective of Haskell's philosophy? If so, I'd love it if an answerer or commenter could explain why.Finally, a lot of the things I've tried have given me errors from the GHC compiler, and many of these Errors have suggested extensions. I am not used to the concept of "extensions" in languages. Are there extensions that I could use to make my code better that are still considered safe/stable/idiomatic?