{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeOperators #-}
module Try.TypeClasses.Theory where
Type classes
-
The part before the
=>is the context, while the part after the=>is the head of the instance declaration. - srcinstance (assertion1, ..., assertion) => class type1 ... typem where ... -
How are type classes implemented in Haskell?
- All You Wanted to Know About Type Classes
-
What is a dictionary?
- A data type with class functions as fields
-
How is it defined and passed into functions? - src
- embed
Superclassdictionary intoSubclassdictionary
newtype BaseD a = BaseD {base :: a -> Bool} data Sub1D a = Sub1D { super1 :: BaseD a , sub1 :: a -> Bool } - embed
-
Passed automatically by the compiler
-
- All You Wanted to Know About Type Classes
-
Why using constraints on a type variable within a data declaration isn't a good idea?
- They make code less flexible and disallow some instances - SO
- Can be achieved by using
GADTs
-
What is coherence and why is it important to maintain it? What are the possible cases of coherence violation?
-
A program is coherent if it has exactly one meaning — i.e., its semantics is unambiguously determined.
Coherenceis when multipletype derivationsare possible - SO- For each different derivation a different class instance can be used. This may lead to different behaviors
FlexibleInstancesandMultiParamTypeClassesintroduce incoherence- Need to maintain coherence to write a program whose type checking (
static) doesn't change its runtime (dynamic) properties
-
-
Overlapping
- How does the instance selection process happen?
- Find an instance with satisfying
Bof (instance A => C B where) - Find instance for
A
- Find an instance with satisfying
- Is it possible to have overlapping instances?
instance C aandinstance C Bool
- Does having overlapping instances violate coherence?
- No
- Basics of Haskell instance selection - src
- Is it possible to have a compiled and working program with coherence violations?
- Yes - src (see example above)
- How would you solve a problem of overlapping instances in various situations?
- Make the most specific instance discoverable using the fine-grained per-instance pragmas
- Rewrite
instance {-# OVERLAPPABLE #-} C aandinstance C Boolinstance C aandinstance {-# OVERLAPPING #-} C BoolOVERLAPS= both
- How does the instance selection process happen?
-
Orphans
- What are orphan instances? Why are they undesirable?
- An orphan instance is a type class instance for class C and type T which is neither defined in the module where C is defined nor in the module where T is defined. - src
- Type class instances are special in that they don't have a name and cannot be
importedexplicitly. This also means that they cannot beexcludedexplicitly. All instances defined in a moduleAare imported automatically when importingA, or importing any module that importsA, directly or indirectly. - Orphans may break the functionality planned by the library author
- Orphans invalidate file fingerprints (hash of a file made by GHC to check later if a file has changed) and transitively - in modules that import them - src
- How to deliver orphans?
- Does having orphan instances violate coherence?
- When orphans violate coherence:
- If you actually import both instances, your program will fail to compile.
- If you do not directly import both, but rather use two modules which independently use the differing instances, you can end up with incoherent behaviour.
- When orphans violate coherence:
- What are the pros and cons of isolating orphans in special modules?
- Pros: less often fingerprints invalidation
- Cons: need to recompile the whole project on changes in that module - src
- What are orphan instances? Why are they undesirable?
-
How the problem of orphans and overlapping is solved in other languages or by different overloading implementation techniques?
- Scala
- An orphan instance in Scala means an instance that exists neither in the type's companion object nor the type class' companion object - src
- Import packages with type and instance declaration separately
- Scala
-
What are the problems of current typeclasses implementation?
- There's no formal proof that instance resolution is coherent
-
Is there a problem of structuring the hierarchy of standard typeclasses?
-
What is Final Tagless (FT) style? - src
- Example:
wimble :: (MonadReader Env m, MonadState State m) => m ()
- Can extend in two dimensions
- a new interpreter (change implementation of
MonadReader) - a new set of operations (add a constraint like
MonadWriter)
- a new interpreter (change implementation of
Application monad(AM) - a monad for organizing effectful application codeFTcan defineAM
Tagged Initial- sum types are represented as(tag, payload).tag- for pattern-matchingTagless Initial- useGADTsto ban nonsense expressions, no tagsFinal Tagless- use overloaded functions
- Example:
-
Functorlaws:fmap id = idfmap (f . g) == fmap f . fmap g
Foldable
class Foldable t where
-
When using folds, one can force the evaluation of an accumulator
-
deepseq- YT -
BangPatterns with pattern matching on the element of an accumulator to force.
-- >>> foldl (\(!a1, !a2) x -> (a1 + x, a2 + x)) (0, 0) [1..9] -- (45,45)
-
-
foldl'- fold a list from the left:f (f (f x a1) a2) ...and have accumulator in WHNF.- May need to force the accumulator
-
foldr- calculate the full list and fold it from the right:f (f (f x a5) a4) .... -
fold :: (Foldable t, Monoid m) => t m -> m-
folds a container with elements that have a
Monoidinstance-- >>> fold [Just "a", Nothing, Just "c"] -- Just "ac"
-
-
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m- maps each element of a container to aMonoidandfolds the container-- >>> foldMap Just ["a", "b", "c"] -- Just "abc"
Alternative and MonadPlus
- Haskell wikibooks:
Alternative-
Definition
class Applicative f => Alternative f where empty :: f a (<|>) :: f a -> f a -> f a -
There's no instance for
Either a -
As it's an associative operation, it produces the same result for either fold
-- >>> foldr (<|>) empty [Just "a", Nothing, Just "c", Nothing, Just "e"] -- Just "a" -- >>> foldl' (<|>) empty [Just "a", Nothing, Just "c", Nothing, Just "e"] -- Just "a"
-
Traversable
-
class (Functor t, Foldable t) => Traversable t where traverse :: (Applicative f) => (a -> f b) -> t a -> f (t b) sequenceA :: (Applicative f) => t (f a) -> f (t a) -- These methods have default definitions. -- They are merely specialised versions of the other two. mapM :: (Monad m) => (a -> m b) -> t a -> m (t b) sequence :: (Monad m) => t (m a) -> m (t a)