02-Constraining-Types-Using-Typeclasses.purs

module Syntax.Basic.Typeclass.Constraints where

import Prelude

-- Adding a Type Class constraint to a type signature
-- enables usage of the corresponding type class' function in that context:

-- Syntax: Adding constraints on Function's type signature
function :: TypeClass1 Type1 => TypeClass2 Type2 => {- and so on -} Type1 -> ReturnType
function arg = "return result"

-- example

class ToInt a where
  toInt :: a -> Int

data List a
  = Nil               -- end of list
  | Cons a (List a)   -- a head element and the rest of the list (tail)

                      -- 'a' must have an 'ToInt' instance for this to compile
stringList_to_intList :: forall a. ToInt a => List a -> List Int
stringList_to_intList Nil = Nil
stringList_to_intList (Cons head tail) = Cons (toInt head) (stringList_to_intList tail)

-- Coupling this with the `forall` syntax:
function0 :: forall a b. TypeClass1 a => TypeClass2 b => a -> b -> String
function0 a b = "return result"



-- Syntax: Adding constraints on type class instances

-- This type class turns any type into a String so we can
-- print it to the console when needed
class Show_ a where -- this is the same signature for Show found in Prelude
  show_ :: a -> String

-- Problem:
-- Say we have a data type called "Box" that just contains a value:
data Boxx a = Boxx a

-- If we want to implement the `Show` typeclass for it, we are limited to this:
instance Show (Boxx a) where
  show (Boxx _) = "Box(<unknown value>)"

{-
We would like to also show the 'a' value stored in Box. How do we do that?
  By constraining our types in the Box to also have a Show instance: -}

-- Syntax
instance (TypeClass1 a) => {-
                   (TypeClassN a) => -} TypeClass1 (IntanceType a) where
  function1 _ = "body"

data Box a = Box a
{- example: Read the following as:
"I can 'show' a Box only if the type stored in the Box can also be shown."
-}
instance (Show a) => Show (Box a) where
  show (Box a) = "Box(" <> show a <> ")"

-- We have names for specific parts of the instance
instance (InstanceContext a) => A_TypeClass (InstanceHead a) where
  function2 _ = "body"

-- Implicit Usage: Since we know that the values below are of type "Box Int"
-- We can use "show" without constraining any types.
test1 :: Boolean
test1 =
  show (Box 4) == "Box(4)"

test2 :: Boolean
test2 =
  show (Box (Box 5)) == "Box(Box(5))"

-- Explicit Usage: The only thing we know about 'a' is that it can be shown.
showIt :: forall a. Show a => a -> String
showIt showableThing = show showableThing

-- All of these work because they all have a Show instance.
test3 :: String
test3 = showIt 4

test4 :: String
test4 = showIt (Box 5)

test5 :: String
test5 = showIt (Box (Box (Box 5)))

-- necessary to make file compile

class TypeClass1 a where
  function1 :: a -> String

class InstanceContext :: Type -> Constraint
class InstanceContext a

instance InstanceContext a

data InstanceHead :: Type -> Type
data InstanceHead a = InstanceHead

class A_TypeClass a where
  function2 :: a -> String

instance TypeClass1 String where
  function1 a = a

class TypeClass2 :: Type -> Constraint
class TypeClass2 a

instance TypeClass2 String

type Type1 = String
type Type2 = String
type ReturnType = String

data IntanceType a = InstanceType a