Test
This commit is contained in:
commit
357629a195
1 changed files with 522 additions and 0 deletions
522
README.org
Normal file
522
README.org
Normal file
|
@ -0,0 +1,522 @@
|
|||
#+TITLE: Type Classes
|
||||
|
||||
#+PROPERTY: header-args:haskell :results replace output
|
||||
#+PROPERTY: header-args:haskell+ :noweb yes
|
||||
#+PROPERTY: header-args:haskell+ :wrap EXAMPLE
|
||||
|
||||
* Summary
|
||||
- Using Ad Hoc Polymorphism with Type classes
|
||||
#+BEGIN_SRC haskell
|
||||
-- How can you write a function to remove duplicates from a list of generic
|
||||
-- elements? You need to rely on the user to provide a function to compare those
|
||||
-- elements `a -> a -> Bool`
|
||||
:{
|
||||
unique :: (a -> a -> Bool) -> [a] -> [a]
|
||||
unique _ [] = []
|
||||
unique f (x:xs) = x : unique f (filter (not . f x) xs)
|
||||
:}
|
||||
|
||||
unique (==) [1, 2, 1, 3, 3, 4, 5, 1]
|
||||
|
||||
-- Works but doesn't scale, more stuff you need to do on a generic `a` and more
|
||||
-- functions you need to ask to the user
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
[1,2,3,4,5]
|
||||
#+end_EXAMPLE
|
||||
|
||||
With type classes you can give a name to a group of related functions and then
|
||||
you can provide an implementation of those functions for different types.
|
||||
- Type class for ~Natural~
|
||||
#+BEGIN_SRC haskell
|
||||
:{
|
||||
class Equality n where
|
||||
equal :: n -> n -> Bool
|
||||
|
||||
instance Equality Int where
|
||||
equal = (==)
|
||||
|
||||
unique :: Equality a => [a] -> [a]
|
||||
unique [] = []
|
||||
unique (x:xs) = x : unique (filter (not . equal x) xs)
|
||||
:}
|
||||
|
||||
unique [1, 2, 1, 3, 3, 4, 5, 1] :: [Int]
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
[1,2,3,4,5]
|
||||
#+end_EXAMPLE
|
||||
- Composing Type Classes
|
||||
#+BEGIN_SRC haskell
|
||||
:{
|
||||
class Equality n where
|
||||
equal :: n -> n -> Bool
|
||||
|
||||
class ToString n where
|
||||
toString :: n -> String
|
||||
|
||||
-- To implement Natural type class for a type you also need to implement
|
||||
-- `Equality` and `ToString`
|
||||
class (Equality n, ToString n) => Natural n where
|
||||
add :: n -> n -> n
|
||||
mul :: n -> n -> n
|
||||
addIdentity :: n
|
||||
mulIdentity :: n
|
||||
:}
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
#+end_EXAMPLE
|
||||
- Creating Default Implementations and Minimal Definitions
|
||||
#+BEGIN_SRC haskell :eval never
|
||||
data Ordering = LT | EQ | GT
|
||||
|
||||
instance Show Ordering where
|
||||
show LT = "LT"
|
||||
show EQ = "EQ"
|
||||
show GT = "GT"
|
||||
|
||||
class Eq a => Ordering a where
|
||||
compare :: a -> a -> Ordering
|
||||
(<) :: a -> a -> Bool
|
||||
(<=) :: a -> a -> Bool
|
||||
(>) :: a -> a -> Bool
|
||||
(>=) :: a -> a -> Bool
|
||||
max :: a -> a -> a
|
||||
min :: a -> a -> a
|
||||
|
||||
-- NOTE: we can implement all the functions in Ordering only based on `compare`
|
||||
-- or `<=`, we can provide default implementations in the class definition and a
|
||||
-- minimal set of function the user need to implement separated by `|`
|
||||
|
||||
class Eq a => Ordering a where
|
||||
compare :: a -> a -> Ordering
|
||||
-- Can be implemented based on `<=`
|
||||
compare a b
|
||||
| a == b = EQ
|
||||
| a <= b = LT
|
||||
| otherwise = GT
|
||||
(<=) :: a -> a -> Bool
|
||||
-- Can be implemented based on `compare`
|
||||
(<=) a b = case compare a b of
|
||||
GT => False
|
||||
_ => True
|
||||
-- ... same as the other functions, therefore either you implement `compare`
|
||||
-- or `<=`
|
||||
{-# MINIMAL compare | (<=) #-}
|
||||
#+END_SRC
|
||||
- Default Implementation only when an Instance of a Type Class is provided
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XDefaultSignatures
|
||||
|
||||
:{
|
||||
-- NOTE: no constraints on `a` in class definition header
|
||||
class Redacted a where
|
||||
redacted :: a -> String
|
||||
-- only when `a` has an instance of `Show` then we can provide a default implementation
|
||||
default redacted :: Show a => a -> String
|
||||
redacted = show
|
||||
|
||||
data Username = Username String
|
||||
data Password = Password String
|
||||
|
||||
instance Show Username where
|
||||
show (Username s) = s
|
||||
|
||||
instance Redacted Username -- ok, implements Show -> we have a default implementation
|
||||
|
||||
-- error, `No instance for (Show Password)` -> no default implementation for `Redacted`
|
||||
-- instance Redacted Password
|
||||
|
||||
instance Redacted Password where
|
||||
redacted _ = "???"
|
||||
:}
|
||||
|
||||
redacted $ Username "Gabriele"
|
||||
redacted $ Password "nosecrets"
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
Gabriele
|
||||
???
|
||||
#+end_EXAMPLE
|
||||
|
||||
- Specifying Type Class Instances with Type Applications
|
||||
TODO
|
||||
|
||||
- Wrapping Types with Newtype
|
||||
TODO
|
||||
|
||||
- Understanding Higher Kinded Types and Polymorphism
|
||||
TODO
|
||||
|
||||
- Deriving Instances (stock)
|
||||
- Don't need language extensions
|
||||
- Works only for (~Eq~, ~Ord~, ~Ix~, ~Show~, ~Read~, ~Enum~, ~Bounded~)
|
||||
- Works only if the underlying types implement the type class
|
||||
- It's not transitive, see ~CustomerWithID~, cannot implement ~Show~ when one
|
||||
of the underglying types doesn't implement ~Show~ even if the type could
|
||||
derive stock ~Show~.
|
||||
#+BEGIN_SRC haskell
|
||||
:{
|
||||
data Customer = Customer
|
||||
{ name :: String
|
||||
, surname :: String
|
||||
, email :: String
|
||||
} deriving (Show, Eq, Ord)
|
||||
|
||||
newtype UserID = UserID String deriving Show
|
||||
:}
|
||||
|
||||
UserID "7246daaf-bf40-4528-a9fe-923cb221cab3"
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
UserID "7246daaf-bf40-4528-a9fe-923cb221cab3"
|
||||
#+end_EXAMPLE
|
||||
|
||||
#+BEGIN_SRC haskell
|
||||
:{
|
||||
newtype UserID = UserID String
|
||||
|
||||
data CustomerWithID = CustomerWithID
|
||||
{ id :: UserID
|
||||
, name :: String
|
||||
, surname :: String
|
||||
, email :: String
|
||||
} deriving Show
|
||||
:}
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
<interactive>:11:14: error:
|
||||
• No instance for (Show UserID)
|
||||
arising from the first field of ‘CustomerWithID’ (type ‘UserID’)
|
||||
Possible fix:
|
||||
use a standalone 'deriving instance' declaration,
|
||||
so you can specify the instance context yourself
|
||||
• When deriving the instance for (Show CustomerWithID)
|
||||
#+end_EXAMPLE
|
||||
|
||||
- Deriving Instances (newtype)
|
||||
- Can derive for newtypes non stock type classes that are implemented by the
|
||||
underlying type
|
||||
- Needs ~GeneralizedNewtypeDeriving~ language extension
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XGeneralizedNewtypeDeriving
|
||||
|
||||
:{
|
||||
newtype EUR = EUR { getCents :: Integer }
|
||||
deriving (Eq, Ord, Show, Enum, Num, Real, Integral)
|
||||
:}
|
||||
|
||||
EUR 200 + EUR 10
|
||||
EUR 200 * 2
|
||||
EUR 200
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
EUR {getCents = 210}
|
||||
EUR {getCents = 400}
|
||||
EUR {getCents = 200}
|
||||
#+end_EXAMPLE
|
||||
|
||||
- Deriving Instances (via)
|
||||
- Can derive the implementation of a typeclass using the implementation of the
|
||||
same typeclass for a type that is /representationally equal/
|
||||
- Needs ~DerivingVia~ language extension
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XKindSignatures
|
||||
:set -XDerivingVia
|
||||
import Data.Kind
|
||||
|
||||
:{
|
||||
-- Define a type with a certain structure
|
||||
newtype First (f :: Type -> Type) (a :: Type) = First (f a) deriving Show
|
||||
|
||||
-- Define some instances
|
||||
instance Semigroup (First Maybe a) where
|
||||
l@(First (Just _)) <> _ = l
|
||||
_ <> r = r
|
||||
|
||||
instance Monoid (First Maybe a) where
|
||||
mempty = First Nothing
|
||||
:}
|
||||
|
||||
(First $ Just [1,2,3]) <> (First $ Just [3,4,5])
|
||||
(First $ Nothing) <> (First $ Just [3,4,5])
|
||||
(First $ Nothing) <> (First $ Nothing)
|
||||
|
||||
:{
|
||||
-- When you have a type that is representationlly equivalent
|
||||
newtype MyMaybe a = MyMaybe (Maybe a)
|
||||
deriving Show
|
||||
-- You can derive via it those instances
|
||||
deriving (Semigroup, Monoid) via (First Maybe a)
|
||||
:}
|
||||
|
||||
(MyMaybe $ Just [1,2,3]) <> (MyMaybe $ Just [3,4,5])
|
||||
(MyMaybe $ Nothing) <> (MyMaybe $ Just [3,4,5])
|
||||
(MyMaybe $ Nothing) <> (MyMaybe $ Nothing)
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
First (Just [1,2,3])
|
||||
First (Just [3,4,5])
|
||||
First Nothing
|
||||
MyMaybe (Just [1,2,3])
|
||||
MyMaybe (Just [3,4,5])
|
||||
MyMaybe Nothing
|
||||
#+end_EXAMPLE
|
||||
|
||||
- Deriving Instances (any)
|
||||
- Can derive a default implmentation without the need to write an empty instance
|
||||
- Needs ~DeriveAnyClass~ language extension
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XDefaultSignatures
|
||||
:set -XDeriveAnyClass
|
||||
|
||||
:{
|
||||
class Redacted a where
|
||||
redacted :: a -> String
|
||||
default redacted :: Show a => a -> String
|
||||
redacted = show
|
||||
|
||||
newtype UserName = UserName String deriving (Show, Redacted)
|
||||
:}
|
||||
|
||||
redacted $ UserName "gabrielelana"
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
<interactive>:12:52: warning: [-Wderiving-defaults]
|
||||
• Both DeriveAnyClass and GeneralizedNewtypeDeriving are enabled
|
||||
Defaulting to the DeriveAnyClass strategy for instantiating Redacted
|
||||
• In the newtype declaration for ‘UserName’
|
||||
Suggested fix:
|
||||
Use DerivingStrategies
|
||||
to pick a different strategy
|
||||
UserName \"gabrielelana\"
|
||||
#+end_EXAMPLE
|
||||
|
||||
- Deriving Strategies
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XDefaultSignatures
|
||||
:set -XDeriveAnyClass
|
||||
:set -XDerivingStrategies
|
||||
|
||||
:{
|
||||
class Redacted a where
|
||||
redacted :: a -> String
|
||||
default redacted :: Show a => a -> String
|
||||
redacted = show
|
||||
|
||||
newtype UserName = UserName String
|
||||
deriving stock Show
|
||||
deriving anyclass Redacted
|
||||
|
||||
newtype Password = Password String
|
||||
|
||||
instance Show Password where
|
||||
show (Password d) = "<redacted>"
|
||||
|
||||
newtype Secret = Secret Password
|
||||
deriving newtype Show
|
||||
deriving anyclass Redacted
|
||||
|
||||
data User = User
|
||||
{ username :: UserName
|
||||
, password :: Password
|
||||
} deriving Show
|
||||
deriving anyclass Redacted
|
||||
:}
|
||||
|
||||
-- Will print `UserName "gabrielelana"` because Show is derived `stock` and
|
||||
-- the default implementation of `Redacted` derived `anyclass` will use `Show`
|
||||
redacted $ UserName "gabrielelana"
|
||||
|
||||
-- Will print `<redacted>` because Show is derived `newtype` from `Password`
|
||||
-- which will print `<redacted>`
|
||||
redacted $ Secret (Password "nosecrets")
|
||||
|
||||
redacted $ User (UserName "gabrielelana") (Password "nosecrets")
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
UserName \"gabrielelana\"
|
||||
<redacted>
|
||||
User {username = UserName \"gabrielelana\", password = <redacted>}
|
||||
#+end_EXAMPLE
|
||||
|
||||
* Exercises
|
||||
- Writing Type Classes Representing Emptiness
|
||||
#+BEGIN_SRC haskell
|
||||
:{
|
||||
import Prelude hiding (null)
|
||||
|
||||
class Nullable a where
|
||||
isNull :: a -> Bool
|
||||
null :: a
|
||||
|
||||
-- Create an instance of Nullable for the following types
|
||||
|
||||
-- `Maybe a` where `a` is Nullable
|
||||
instance Nullable a => Nullable (Maybe a) where
|
||||
isNull (Just a) = isNull a
|
||||
isNull Nothing = True
|
||||
|
||||
null = Nothing
|
||||
|
||||
-- `(a, b)` where `a` and `b` are Nullable
|
||||
instance (Nullable a, Nullable b) => Nullable (a, b) where
|
||||
isNull (a, b) = isNull a && isNull b
|
||||
null = (null, null)
|
||||
|
||||
-- `[a]`
|
||||
instance Nullable [a] where
|
||||
isNull [] = True
|
||||
isNull _ = False
|
||||
null = []
|
||||
:}
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
#+end_EXAMPLE
|
||||
- Add a Default Null Test
|
||||
#+BEGIN_SRC haskell
|
||||
import Prelude hiding (null)
|
||||
|
||||
-- Given an Eq constraint on Nullable create a default implementation of isNull
|
||||
:{
|
||||
class Eq a => Nullable a where
|
||||
isNull :: a -> Bool
|
||||
isNull = (==) null
|
||||
|
||||
null :: a
|
||||
|
||||
-- Alternative with constraint only for isNull
|
||||
|
||||
-- class Nullable a where
|
||||
-- default Eq a => isNull :: a -> Bool
|
||||
-- isNull = (==) null
|
||||
--
|
||||
-- null :: a
|
||||
|
||||
instance Eq a => Nullable [a] where
|
||||
null = []
|
||||
:}
|
||||
|
||||
isNull []
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
True
|
||||
#+end_EXAMPLE
|
||||
- Deriving Nullable
|
||||
#+BEGIN_SRC haskell
|
||||
:set -XKindSignatures
|
||||
:set -XDerivingVia
|
||||
import Data.Kind
|
||||
|
||||
:{
|
||||
class Nullable a where
|
||||
isNull :: a -> Bool
|
||||
null :: a
|
||||
|
||||
instance Nullable [a] where
|
||||
isNull [] = True
|
||||
isNull _ = False
|
||||
null = []
|
||||
|
||||
instance Nullable (Maybe a) where
|
||||
isNull Nothing = True
|
||||
isNull _ = False
|
||||
null = Nothing
|
||||
|
||||
newtype Shallow (f :: Type -> Type) (a :: Type) = Shallow (f a)
|
||||
|
||||
instance Nullable (Shallow Maybe a) where
|
||||
isNull (Shallow Nothing) = True
|
||||
isNull _ = False
|
||||
null = Shallow Nothing
|
||||
|
||||
instance Nullable (Shallow [] a) where
|
||||
isNull (Shallow []) = True
|
||||
isNull _ = False
|
||||
null = Shallow []
|
||||
|
||||
newtype Deep (f :: Type -> Type) (a :: Type) = Deep (f a)
|
||||
|
||||
instance Nullable a => Nullable (Deep Maybe a) where
|
||||
isNull (Deep Nothing) = True
|
||||
isNull (Deep (Just a)) = isNull a
|
||||
null = Deep Nothing
|
||||
|
||||
instance Nullable a => Nullable (Deep [] a) where
|
||||
isNull (Deep []) = True
|
||||
isNull (Deep xs) = all isNull xs
|
||||
null = Deep []
|
||||
|
||||
newtype W1 = W1 (Maybe [Int])
|
||||
deriving Nullable via (Shallow Maybe [Int])
|
||||
|
||||
newtype W2 = W2 (Maybe [Int])
|
||||
deriving Nullable via (Deep Maybe [Int])
|
||||
|
||||
newtype W3 = W3 ([Maybe Int])
|
||||
deriving Nullable via (Shallow [] (Maybe Int))
|
||||
|
||||
newtype W4 = W4 ([Maybe Int])
|
||||
deriving Nullable via (Deep [] (Maybe Int))
|
||||
:}
|
||||
|
||||
-- Shallow Maybe [Int]
|
||||
False == isNull (W1 (Just []))
|
||||
True == isNull (W1 Nothing)
|
||||
|
||||
-- Deep Maybe [Int]
|
||||
True == isNull (W2 Nothing)
|
||||
True == isNull (W2 (Just []))
|
||||
False == isNull (W2 (Just [1]))
|
||||
|
||||
-- Shallow [Maybe Int]
|
||||
True == isNull (W3 [])
|
||||
False == isNull (W3 [Nothing])
|
||||
False == isNull (W3 [Just 1])
|
||||
|
||||
-- Deep [Maybe Int]
|
||||
True == isNull (W4 [])
|
||||
True == isNull (W4 [Nothing])
|
||||
True == isNull (W4 [Nothing, Nothing])
|
||||
False == isNull (W4 [Just 1])
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
#+begin_EXAMPLE
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
True
|
||||
#+end_EXAMPLE
|
Loading…
Add table
Reference in a new issue