DerivingVia or or, Ho , How t to T o Tur urn H n Hand - - PowerPoint PPT Presentation

derivingvia
SMART_READER_LITE
LIVE PREVIEW

DerivingVia or or, Ho , How t to T o Tur urn H n Hand - - PowerPoint PPT Presentation

DerivingVia or or, Ho , How t to T o Tur urn H n Hand and-Writt itten en Ins nstan tances i es into an o an Ant Anti-P -Patt attern rn Baldur Blndal Ryan Scott 1 Andres Lh 2 1 Indiana University 2 Well-Typed LLP


slide-1
SLIDE 1

DerivingVia

  • r
  • r, Ho

, How t to T

  • Tur

urn H n Hand and-Writt itten en Ins nstan tances i es into an

  • an Ant

Anti-P

  • Patt

attern rn

rgscott@indiana.edu github.com/RyanGlScott

Baldur Blöndal Ryan Scott1 Andres Löh2

1Indiana University 2Well-Typed LLP

slide-2
SLIDE 2

A brief history of deriving deriving

Haskell 98 Report (various authors): Derive the Blessed Type Classes™ data Grade = A | B | C | D | F instance Eq Grade where A == A = True B == B = True ... _ == _ = False

slide-3
SLIDE 3

A brief history of deriving deriving

Haskell 98 Report (various authors): Derive the Blessed Type Classes™ data Grade = A | B | C | D | F deriving Eq

slide-4
SLIDE 4

A brief history of deriving deriving

Haskell 98 Report (various authors): Derive the Blessed Type Classes™ data Grade = A | B | C | D | F deriving ( Eq, Ord, Read, Show , Enum, Bounded, Ix )

slide-5
SLIDE 5

A brief history of deriving deriving

GHC extensions (various authors): Derive these other blessed classes {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} data Grade = A | B | C | D | F deriving ( Data, Generic , ... )

slide-6
SLIDE 6

A brief history of deriving deriving

GHC extension (Peyton Jones, 2001): Derive anything (through newtypes) instance Num Int where ... newtype Age = MkAge Int instance Num Age where MkAge x + MkAge y = MkAge (x + y) ...

slide-7
SLIDE 7

A brief history of deriving deriving

GHC extension (Peyton Jones, 2001): Derive anything (through newtypes) {-# LANGUAGE GeneralizedNewtypeDeriving #-} instance Num Int where ... newtype Age = MkAge Int deriving Num

slide-8
SLIDE 8

A brief history of deriving deriving

GHC extension (Magalhães, 2014): Derive anything (through empty instances) class Foo a where bar :: a -> String bar _ = “Sensible default” data Baz = MkBaz instance Foo Baz

slide-9
SLIDE 9

A brief history of deriving deriving

GHC extension (Magalhães, 2014): Derive anything (through empty instances) {-# LANGUAGE DeriveAnyClass #-} class Foo a where bar :: a -> String bar _ = “Sensible default” data Baz = MkBaz deriving Foo

slide-10
SLIDE 10

A brief history of deriving deriving

GHC extension (Scott, 2016): Be explicit about how you derive things {-# LANGUAGE DerivingStrategies #-} newtype Age = MkAge Int deriving stock (Eq, Ord) deriving newtype Num deriving anyclass Bar

slide-11
SLIDE 11
slide-12
SLIDE 12

class Monoid a where mempty :: a mappend :: a -> a -> a class Applicative f where pure :: a -> f a liftA2 :: (a -> b -> c) -> f a -> f b -> f c

slide-13
SLIDE 13

instance Monoid a => Monoid (IO a) where mempty = pure mempty mappend = liftA2 mappend

slide-14
SLIDE 14

instance Monoid a => Monoid (ST s a) where mempty = pure mempty mappend = liftA2 mappend

slide-15
SLIDE 15

instance Monoid b => Monoid (a -> b) where mempty = pure mempty mappend = liftA2 mappend

slide-16
SLIDE 16

instance (Monoid a, Monoid b) => Monoid (a, b) where mempty = pure mempty mappend = liftA2 mappend

slide-17
SLIDE 17

instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend

slide-18
SLIDE 18

instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend instance Alternative f => Monoid (f a) where mempty = empty mappend = (<|>)

slide-19
SLIDE 19

instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend instance Alternative f => Monoid (f a) where mempty = empty mappend = (<|>)

slide-20
SLIDE 20

instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend

Can we abstract this pattern out without the pain of instance overlap?

slide-21
SLIDE 21

instance (Applicative f, Monoid a) => Monoid (f a) where mempty = pure mempty mappend = liftA2 mappend

Can we abstract this pattern out without the pain of instance overlap?

Well, sort of...

slide-22
SLIDE 22

newtype Ap f a = Ap { getAp :: f a }

Can we abstract this pattern out without the pain of instance overlap?

Well, sort of...

slide-23
SLIDE 23

newtype Ap f a = Ap { getAp :: f a }

instance (Applicative f, Monoid a) => Monoid (Ap f a) where mempty = Ap (pure mempty) mappend (Ap fa) (Ap fb) = Ap (liftA2 mappend fa fb) Can we abstract this pattern out without the pain of instance overlap?

Well, sort of...

slide-24
SLIDE 24

instance Monoid a => Monoid (IO a) where mempty = pure mempty mappend = liftA2 mappend

slide-25
SLIDE 25

instance Monoid a => Monoid (IO a) where mempty = getAp (mempty :: Ap IO a) mappend p1 p2 = getAp (mappend (Ap p1) (Ap p2) :: Ap IO a)

slide-26
SLIDE 26

instance Monoid a => Monoid (IO a) where mempty = getAp (mempty :: Ap IO a) mappend p1 p2 = getAp (mappend (Ap p1) (Ap p2) :: Ap IO a)

( ° ° ╯ □ )╯︵ )╯︵ ┻━┻ )

slide-27
SLIDE 27
slide-28
SLIDE 28

“deriving ought to be able to

write this code for you!”

slide-29
SLIDE 29

instance Monoid a => Monoid (IO a) where mempty = getAp (mempty :: Ap IO a) mappend p1 p2 = getAp (mappend (Ap p1) (Ap p2) :: Ap IO a)

slide-30
SLIDE 30

data IO a = ... deriving Monoid ???

slide-31
SLIDE 31

data IO a = ... deriving Monoid via (Ap IO a)

slide-32
SLIDE 32
slide-33
SLIDE 33

{-# LANGUAGE GeneralizedNewtypeDeriving #-} instance Num Int where ... newtype Age = MkAge Int deriving newtype Num

slide-34
SLIDE 34

instance Num Int where ... newtype Age = MkAge Int instance Num Age where MkAge x + MkAge y = MkAge (x + y) ...

slide-35
SLIDE 35

instance Num Int where ... newtype Age = MkAge Int instance Num Age where (+) = unsafeCoerce ((+) :: Int -> Int -> Int)

slide-36
SLIDE 36

instance Num Int where ... newtype Age = MkAge Int instance Num Age where (+) = unsafeCoerce ((+) :: Int -> Int -> Int)

unsafeCoerce unsafeCoerce :: a -> b

slide-37
SLIDE 37

instance Num Int where ... newtype Age = MkAge Int instance Num Age where (+) = {- unsafeCoerce -} coerce ((+) :: Int -> Int -> Int)

unsafeCoerce unsafeCoerce :: a -> b coerce coerce :: Coercible a b => a -> b

slide-38
SLIDE 38

coerce coerce :: Coercible a b => a -> b

Only typechecks if types a and b have the same runtime representation.

slide-39
SLIDE 39

coerce coerce :: Coercible a b => a -> b

Only typechecks if types a and b have the same runtime representation. newtype Age = MkAge Int

slide-40
SLIDE 40

coerce coerce :: Coercible a b => a -> b

Only typechecks if types a and b have the same runtime representation. newtype Age = MkAge Int instance Coercible Age Int instance Coercible Int Age

slide-41
SLIDE 41

coerce coerce :: Coercible a b => a -> b

Only typechecks if types a and b have the same runtime representation. newtype Age = MkAge Int instance Coercible (Age -> Age) (Int -> Int) instance Coercible (Int -> Int) (Age -> Age)

slide-42
SLIDE 42

coerce coerce :: Coercible a b => a -> b

Only typechecks if types a and b have the same runtime representation. newtype Age = MkAge Int succInt :: Int -> Int succInt i = i + 1 succAge :: Age -> Age succAge = coerce succInt

slide-43
SLIDE 43

data IO a = ... deriving Monoid via (App IO a)

slide-44
SLIDE 44

data IO a = ... deriving Monoid via (App IO a)

instance Monoid a => Monoid (IO a) where mempty = coerce (mempty :: Ap IO a) mappend = coerce (mappend :: Ap IO a

  • > Ap IO a
  • > Ap IO a)
slide-45
SLIDE 45

data IO a = ... deriving Monoid via (App IO a)

instance Monoid a => Monoid (IO a) where mempty = coerce (mempty :: Ap IO a) mappend = coerce (mappend :: Ap IO a

  • > Ap IO a
  • > Ap IO a)

Typechecks since IO a and Ap IO a have the same runtime representation.

slide-46
SLIDE 46

De Deriv iving ngVia ia is generalized GeneralizedNewtypeDeriving GeneralizedNewtypeDeriving

slide-47
SLIDE 47

De Deriv iving ngVia ia is generalized GeneralizedNewtypeDeriving GeneralizedNewtypeDeriving

newtype Age = MkAge Int deriving newtype Num ==> instance Num Age where (+) = coerce ((+) :: Int -> Int -> Int) ...

slide-48
SLIDE 48

De Deriv iving ngVia ia is generalized GeneralizedNewtypeDeriving GeneralizedNewtypeDeriving

newtype Age = MkAge Int deriving Num via Int ==> instance Num Age where (+) = coerce ((+) :: Int -> Int -> Int) ...

slide-49
SLIDE 49

Case studies

  • QuickCheck

QuickCheck

  • Excluding types in datatype-generic algorithms
slide-50
SLIDE 50

Case study: QuickCheck QuickCheck

QuickCheck is a library for testing random properties of Haskell programs. class Arbitrary a where arbitrary :: Gen a

  • - Generate random ‘a’ values
slide-51
SLIDE 51

Case study: QuickCheck QuickCheck

QuickCheck is a library for testing random properties of Haskell programs. class Arbitrary a where arbitrary :: Gen a

  • - Generate random ‘a’ values

> sample’ (arbitrary :: Gen Bool) [False,False,False,True,True,True,False,True, False,False,True]

slide-52
SLIDE 52

Case study: QuickCheck QuickCheck

QuickCheck is a library for testing random properties of Haskell programs. class Arbitrary a where arbitrary :: Gen a

  • - Generate random ‘a’ values

> sample’ (arbitrary :: Gen Int) [0,1,-1,-6,6,-7,5,13,1,8,1]

slide-53
SLIDE 53

Case study: QuickCheck QuickCheck

QuickCheck is a library for testing random properties of Haskell programs. class Arbitrary a where arbitrary :: Gen a

  • - Generate random ‘a’ values

> sample’ (arbitrary :: Gen [Int]) [[],[],[3],[1],[1,1,-6,5,-5],[],[7,-11,7],[5], [-16,15,-14,-12,5,-11],[6,1,-8,-16,9,1,15,4,-5,

  • 18,-15,-18,-2],[-16,17,9,-3,-13,-9,11,-18,
  • 6,8,1,-4,-5,-1,-17]]
slide-54
SLIDE 54

Q: What if we want to generate random values subject to constraints?

slide-55
SLIDE 55

Q: What if we want to generate random values subject to constraints? A: Use newtypes!

newtype NonEmptyList a = NonEmpty [a]

slide-56
SLIDE 56

Q: What if we want to generate random values subject to constraints? A: Use newtypes!

newtype NonEmptyList a = NonEmpty [a] instance Arbitrary a => Arbitrary (NonEmptyList a) where arbitrary = fmap NonEmpty (arbitrary `suchThat` (not . null))

slide-57
SLIDE 57

newtype Nums = MkNums [Int] deriving newtype Arbitrary > sample’ (arbitrary :: Gen Nums) [[],[0,-2],[],[0,-2,-3],[5,4,-5,5],[9,0], [-5,1,-5,2,11]]

slide-58
SLIDE 58

newtype newtype NonEmptyList NonEmptyList a a = NonEmpty NonEmpty [a] [a] newtype Nums = MkNums [Int] deriving Arbitrary via (NonEmptyList NonEmptyList Int) > sample’ (arbitrary :: Gen Nums) [[2,1],[1],[-3,2],[-6,3,-4,6],[-1,6,7,4,-3], [2,10,9,-7,8,-9,-7,4,4],[12,5,5,9,10]]

slide-59
SLIDE 59

newtype NonEmptyList a = NonEmpty [a] newtype newtype Positive Positive a a = MkPositive MkPositive a a newtype Nums = MkNums [Int] deriving Arbitrary via (NonEmptyList (Positive Positive Int)) > sample’ (arbitrary :: Gen Nums) [[2],[1,2],[3,4],[2,5],[1],[8,2,4,3,4,5,1,7], [10,6,2,11,10,3,2,11,12]]

slide-60
SLIDE 60

newtype NonEmptyList a = NonEmpty [a] newtype Positive a = MkPositive a newtype newtype Large Large a a = MkLarge MkLarge a a newtype Nums = MkNums [Int] deriving Arbitrary via (NonEmptyList (Positive (Large Large Int))) > sample’ (arbitrary :: Gen Nums) [[2],[2,1],[2,7,8,4],[11,13], [8,40,17,57,16,51,88,58],[249,27],[511,642]]

slide-61
SLIDE 61

Case studies

  • QuickCheck
  • Excluding types in datatype-generic algorithms
slide-62
SLIDE 62

Case study: Excluding types

data ModIface { ... , mi_fixities :: [(OccName,Fixity)] , mi_fix_fn :: OccName -> Maybe Fixity

  • - ^ Cached lookup for 'mi_fixities'

... } How can we derive instances for felds with problematic types?

slide-63
SLIDE 63

Case study: Excluding types

data ModIface { ... , mi_fixities :: [(OccName,Fixity)] , mi_fix_fn :: OccName -> Maybe Fixity

  • - ^ Cached lookup for 'mi_fixities'

... } deriving Eq via (Excluding ‘[OccName -> Maybe Fixity] ModIface) How can we derive instances for felds with problematic types?

slide-64
SLIDE 64

Case study: Excluding types

data ModIface { ... , mi_fixities :: [(OccName,Fixity)] , mi_fix_fn :: OccName -> Maybe Fixity

  • - ^ Cached lookup for 'mi_fixities'

... } deriving Eq via (Excluding ‘[OccName -> Maybe Fixity] ModIface) deriving stock Generic How can we derive instances for felds with problematic types?

slide-65
SLIDE 65

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool

slide-66
SLIDE 66

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance GEq excluded U1 where geq U1 U1 = True

slide-67
SLIDE 67

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance (GEq excluded a, GEq excluded b) => GEq excluded (a :*: b) where geq (a1 :*: b1) (a2 :*: b2) = geq @excluded a1 a2 && geq @excluded b1 b2

slide-68
SLIDE 68

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance (GEq excluded a, GEq excluded b) => GEq excluded (a :+: b) where geq (L1 a) (L1 b) = geq @excluded a b geq (R1 a) (R1 b) = geq @excluded a b geq _ _ = False

slide-69
SLIDE 69

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ??? => GEq excluded (Rec0 a) where ???

slide-70
SLIDE 70

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance (Eq a) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = (x == y)

slide-71
SLIDE 71

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance (Eq a) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = (x == y)

slide-72
SLIDE 72

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , ??? ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ???

slide-73
SLIDE 73

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , ??? ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ??? type family Unless (a :: Bool) (b :: Constraint) :: Constraint where Unless True _ = () Unless False b = b type family Elem (x :: a) (xs :: [a]) :: Bool where Elem _ '[] = False Elem x (x:_) = True Elem x (y:xs) = Elem x xs

slide-74
SLIDE 74

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , ??? ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ???

slide-75
SLIDE 75

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , SBoolI (Elem a excluded) ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ???

slide-76
SLIDE 76

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , SBoolI (Elem a excluded) ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ??? data SBool :: Bool -> Type where SFalse :: SBool False STrue :: SBool True

slide-77
SLIDE 77

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , SBoolI (Elem a excluded) ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ??? data SBool :: Bool -> Type where SFalse :: SBool False STrue :: SBool True class SBoolI (b :: Bool) where sbool :: SBool b instance SBoolI False where sbool = SFalse instance SBoolI True where sbool = STrue

slide-78
SLIDE 78

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , SBoolI (Elem a excluded) ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) = ???

slide-79
SLIDE 79

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool instance ( Unless (Elem a excluded) (Eq a) , SBoolI (Elem a excluded) ) => GEq excluded (Rec0 a) where geq (K1 x) (K1 y) =

case sbool @(Elem a excluded) of SFalse -> (x == y) STrue -> True

slide-80
SLIDE 80

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool

newtype Excluding :: [Type] -> Type

  • > Type where

Excluding :: a -> Excluding excluded a

slide-81
SLIDE 81

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool

instance (Generic a, GEq excluded (Rep a)) => Eq (Excluding excluded a) where Excluding x == Excluding y = geq @excluded (from x) (from y)

slide-82
SLIDE 82

import GHC.Generics class GEq (excluded :: [Type]) f where geq :: f a -> f a -> Bool

instance (Generic a, GEq excluded (Rep a)) => Eq (Excluding excluded a) where Excluding x == Excluding y = geq @excluded (from x) (from y)

data ModIface { ... , mi_fixities :: [(OccName,Fixity)] , mi_fix_fn :: OccName -> Maybe Fixity

  • - ^ Cached lookup for 'mi_fixities'

... } deriving Eq via (Excluding ‘[OccName -> Maybe Fixity] ModIface) deriving stock Generic

slide-83
SLIDE 83

Debut ebuts in GH s in GHC 8 C 8.6! 6!

deriving via deriving via lets you quickly write your type class instances with a high power-to-weight ratio.

  • Allows effective use of newtypes without the awkwardness of

wrapping/unwrapping them yourself

  • Leverage existing tools in GHC in a way that feels natural
  • Compose programming patterns by codifying them as

newtypes, cheaply and cheerfully