Skip to content

Instantly share code, notes, and snippets.

@hanshoglund
Last active June 8, 2021 21:32
Show Gist options
  • Save hanshoglund/82e7dc3ce42217f4ac92b456da5f5afb to your computer and use it in GitHub Desktop.
Save hanshoglund/82e7dc3ce42217f4ac92b456da5f5afb to your computer and use it in GitHub Desktop.

(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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment