Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Blog Post: Derive instances of representationally equal types

Reddit discusson thread.

I made a way to get more free stuff and free stuff is good.

The current implementation of deriveVia is here, it works with all the examples here. Needs GHC 8.2 and th-desugar.

It doesn't take long

for new Haskellers to get pampered by their compiler. For the price of a line or two the compiler offers to do your job, to write uninteresting code for you (in the form of type classes) such as equality, comparison, serialization, ... in the case of 3-D vectors

-- Eq   :: Type -> Constraint
-- Ord  :: Type -> Constraint
-- Show :: Type -> Constraint
-- Read :: Type -> Constraint

data V3 a = V3 a a a
  deriving (Eq, Ord, Show, Read, ...)

In the distant past GHC could only be cajoled into defining a few classes hard-coded into the compiler. With time that list grew to include more interesting classes — type classes over type constructors (of kind Type -> Type) rather than simple types (Type) — but always at the discretion of compiler writers.

{-# Language DeriveTraversable #-}

-- Functor     :: (Type -> Type) -> Constraint
-- Foldable    :: (Type -> Type) -> Constraint
-- Traversable :: (Type -> Type) -> Constraint

data V3 a = V3 a a a
  deriving (..., Functor, Foldable, Traversable)

With the advent of default methods and Generic the rubber band was on the other claw, library writers could now specify a generic, singular (privileged) function to be the default implementation of certain methods.

The JSON-library aeson provides default implementations of JSON serialization

class ToJSON a where
  toJSON :: a -> Value
  toJSON = genericToJSON defaultOptions
  default 
    toJSON :: (Generic a, GToJSON Value Zero (Rep a)) => a -> Value

class FromJSON a where
  parseJSON :: Value -> Parser a
  parseJSON = genericParseJSON defaultOptions
  default 
    parseJSON :: (Generic a, GFromJSON Zero (Rep a)) => Value -> Parser a

so users don't even have to specify them

{-# Language DeriveGeneric #-}

import GHC.Generics (Generic)
import Data.Aeson   (ToJSON, FromJSON)

data V3 a = V3 a a a
  deriving 
    (..., Generic)

instance   ToJSON a =>   ToJSON (V3 a)
instance FromJSON a => FromJSON (V3 a)

Then we got the option of deriving any class like this

{-# Language ..., DeriveAnyClass #-}

data V3 a = V3 a a a
  deriving 
    (..., Generic, ToJSON, FromJSON)

and with the latest release (GHC 8.2) we get the option to be more explicit

{-# Language ..., DerivingStrategies #-}

data V3 a = V3 a a a
  deriving 
    (Eq, Ord, Show, Read, Generic)
    
  deriving 
    (Functor, Foldable, Traversable)

  deriving anyclass
    (ToJSON, FromJSON)
@Icelandjack
Copy link
Author

Icelandjack commented Jul 2, 2017

There are many more applications, some allow us to avoid boilerplate code and others allow us to codify ‘common Haskell knowledge’ (like getting Num from Applicative).

Another example of such knowledge is that Monad can be defined in terms of a Functor with return and join:

class Functor m => MonadJoin m where
  return_  :: a -> m a
  join_    :: m (m a) -> m a

so given an instance for MonadJoin we can derive Monad via WrappedMonadJoin

data MAYBE a = NOTHING | JUST a
  deriving 
    Functor
  deriving via WrappedMonad
    Applicative
  deriving via WrappedMonadJoin
    Monad

instance MonadJoin MAYBE where
  return_ :: a -> MAYBE a
  return_ = JUST

  join_ :: MAYBE (MAYBE a) -> MAYBE a
  join_ (JUST (JUST a)) = JUST a
  join_ _               = NOTHING

Sometimes join is more intuitive, we may also wish to specify Applicative in terms of the equivalent Monoidal using WrappedMonoidal

class Functor f => Monoidal f where
  unit :: f ()
  (**) :: f a -> f b -> f (a,b)
data MAYBE a = ...
  deriving via WrappedMonoidal
    Applicative

instance Monoidal MAYBE where
  unit :: MAYBE ()
  unit = JUST ()

  (**) :: MAYBE a -> MAYBE b -> MAYBE (a, b)
  JUST a ** JUST b = JUST (a, b)
  _      ** _      = NOTHING

It also allows us to work with crazy hierarchies like this where we can derive everything..

@Icelandjack
Copy link
Author

Icelandjack commented Jul 2, 2017

I will write more posts about more interesting deriving schemes but for now, what do you think?

@andorp
Copy link

andorp commented Jul 3, 2017

Please keep writing.

@Icelandjack
Copy link
Author

Icelandjack commented Jul 5, 2017

@andorp I'm already working on Part 2.

@andrewthad
Copy link

andrewthad commented Aug 10, 2017

Here's another use case: You can trivially get Storable from the more expressive Primitive.

@Icelandjack
Copy link
Author

Icelandjack commented Sep 7, 2017

Thanks for the suggestion @andrewthad, maybe you know how to complete the instance declaration

newtype WrappedPrim a = WrapPrim a
  deriving newtype
    Prim

instance Prim a => Storable (WrappedPrim a) where
  sizeOf :: WrappedPrim a -> Int
  sizeOf a = I# (sizeOf# a)

  alignment :: WrappedPrim a -> Int
  alignment a = I# (alignment# a)

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