(Quick notes written up in response to a discussion on defaulting in GHC.)
Though I argued that implicitly imported defaults should be opt-in, I have a use case for them in this library. It makes heavy use of "custom literals", defined thus:
class IsPitch a where
c :: a
d :: a
-- etc
This is basically "IsString
on steroids". Compare:
2 :: Num a => a
"x" :: IsString a => a
c :: IsPitch a => a
There are various instances for IsPitch
, with different trade-offs in terms of expressivity. Notably we also have container instances such as
instance IsPitch a => IsPitch (Chord a) where
...
instance IsPitch a => IsPitch (Score a) where
...
This allow us to build polymorphic music expressions like the below:
-- Two notes at the same time (a chord)
c <> d :: (Monoid a, IsPitch a) => a
-- Two notes in sequence (a melody)
c |> d :: (Monoid a, HasPosition a, IsPitch a) => a
-- A melody with dynamics
foldr1 (|>) [c,d,level pp e] :: (Monoid a, HasPosition a, IsPitch a, HasDynamics a) => a
Ambiguity errors occur when we combine these expresisons with monomorphizing functions. For example we have a function called defaultMain :: (IsPitch a, ...) => Score a -> IO ()
, designed to be used with main
like this:
main = defaultMain (c |> d)
main = defaultMain (c :: Pitch)
main = defaultMain (c :: Score Pitch)
This is in a way an encoding allowing more than one type for main, which is supported by e.g. Elm.