Skip to content

Instantly share code, notes, and snippets.

Last active October 7, 2019 22:43
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
    toJSON :: (Generic a, GToJSON Value Zero (Rep a)) => a -> Value

class FromJSON a where
  parseJSON :: Value -> Parser a
  parseJSON = genericParseJSON defaultOptions
    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
    (..., 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
    (..., 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
    (Eq, Ord, Show, Read, Generic)
    (Functor, Foldable, Traversable)

  deriving anyclass
    (ToJSON, FromJSON)
Copy link

@andorp I'm already working on Part 2.

Copy link

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

Copy link

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

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