{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
-- | Notes taken by Julija Mikeliūnaitė
module Lessons.Lesson11 where

import Control.Exception
import Control.DeepSeq

-- | Haskell has exceptions. The error function is the simplest way to throw exceptions. error is a pure function and can be any type. Here, the error function works as an integer, therefore it can be returned.
--
foo :: Integer
foo :: Integer
foo = String -> Integer
forall a. HasCallStack => String -> a
error String
"Oops"

-- | For exception cathcing you need the Control.Exception package. The try function needs an IO block.
--
c :: Exception e => IO (Either e Integer)
c :: forall e. Exception e => IO (Either e Integer)
c = IO Integer -> IO (Either e Integer)
forall e a. Exception e => IO a -> IO (Either e a)
try (Integer -> IO Integer
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Integer
forall a. HasCallStack => String -> a
error String
"Ooops"))

-- | If the code is multithreaded, you can send exceptions to any thread.

-- | Haskell is considered a lazy programming language. In a lazy language computations are only made if absolutely necessary.

-- | If one of the items in the list is an error, the final result will be the error. This is an example of a strict/eager function.
--
-- >>> eager
-- oj
eager :: [Integer]
eager :: [Integer]
eager = [Integer
1,Integer
2,Integer
3,Integer
4, String -> Integer
forall a. HasCallStack => String -> a
error String
"oj"]

-- | The take x function takes the first x elements from the list and returns them. This is an example of a lazy function.
--
-- >>> lazy
-- [1,2]
lazy :: [Integer]
lazy :: [Integer]
lazy = Int -> [Integer] -> [Integer]
forall a. Int -> [a] -> [a]
take Int
2 [Integer
1,Integer
2,Integer
3,Integer
4, String -> Integer
forall a. HasCallStack => String -> a
error String
"ooops"]

-- | length is also a lazy function, because it counts how many items are in a list but does not care about their type (even if some of them may be errors).
--
-- >>> lazy'
-- 1
lazy' :: Int
lazy' :: Int
lazy' = [Any] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [String -> Any
forall a. HasCallStack => String -> a
error String
"ooops"]

-- | With take 1, if the first element is an error, then the result is the error.
--
-- >>> lazy''
-- ooops
lazy'' :: [Integer]
lazy'' :: [Integer]
lazy'' = Int -> [Integer] -> [Integer]
forall a. Int -> [a] -> [a]
take Int
1 [String -> Integer
forall a. HasCallStack => String -> a
error String
"ooops"]

-- | Even though the integer list is infinite, first 10 works fine, because of the laziness. If you were to print out the whole list [1..], it would just keep going forever to infinity.
-- 
-- >>> first 10
-- [1,2,3,4,5,6,7,8,9,10]
first :: Int -> [Integer]
first :: Int -> [Integer]
first Int
n = Int -> [Integer] -> [Integer]
forall a. Int -> [a] -> [a]
take Int
n [Integer
1..]

-- | Lists are constructed using :, therefore the first n elements can be checked one by one.

-- | Eager calculations - trying to compute all data at once and store everything in memory. It can be achieved using libraries with the keyword "Strict".

-- | seq function: 
--
-- seq :: a -> b -> b
-- Simplest explanation: the return value is bottom if a is bottom, otherwise it is b.

-- | Returns 5 and ensures that everything was calculated/checked.
--
-- >>> seq 4 5
-- 5

-- >>> seq (error "Aj") 5
-- Aj

-- | Returns 5, because seq removes the top layer of what is uncalculated.
--
-- >>> seq [error "Aj"] 5
-- 5

-- | Does all of the calculations, no matter the layer.
--
-- >>> deepseq ([error "Aj"]:: [Int]) 5
-- Aj

-- | DSL - domain specific language. In our DSL's we had differend commands - a dictionary of tasks which we can use within our language. A classic example - calculator.

-- Initial
-- Language
-- | Expr (expression) can be either a literal (number) or an operation for two expressions (add, mul) or an operation for one expression (neg). This is already a DSL and now we can write programs using it.
-- 
data Expr = Lit Integer
          | Add Expr Expr
          | Mul Expr Expr
          | Neg Expr
          deriving Int -> Expr -> ShowS
[Expr] -> ShowS
Expr -> String
(Int -> Expr -> ShowS)
-> (Expr -> String) -> ([Expr] -> ShowS) -> Show Expr
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Expr -> ShowS
showsPrec :: Int -> Expr -> ShowS
$cshow :: Expr -> String
show :: Expr -> String
$cshowList :: [Expr] -> ShowS
showList :: [Expr] -> ShowS
Show

-- | Program
--
prog1 :: Expr
prog1 :: Expr
prog1 = Expr -> Expr
Neg (Expr -> Expr -> Expr
Add (Integer -> Expr
Lit Integer
5) (Expr -> Expr -> Expr
Add (Integer -> Expr
Lit Integer
4) (Integer -> Expr
Lit Integer
6)))

-- | This is used to define how the program should act with different expressions.
--
-- Evaluate
-- >>> eval prog1
-- -15
eval :: Expr -> Integer
eval :: Expr -> Integer
eval (Lit Integer
i) = Integer
i
eval (Add Expr
e1 Expr
e2) = (Expr -> Integer
eval Expr
e1) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ (Expr -> Integer
eval Expr
e2)
eval (Mul Expr
e1 Expr
e2) = (Expr -> Integer
eval Expr
e1) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* (Expr -> Integer
eval Expr
e2)
eval (Neg Expr
e) = - (Expr -> Integer
eval Expr
e)

-- | This is used for printing different expressions to the console.
--
-- >>> print' prog1
-- "-(5 + 4 + 6)"
print' :: Expr -> String
print' :: Expr -> String
print' (Lit Integer
i) = Integer -> String
forall a. Show a => a -> String
show Integer
i
print' (Add Expr
e1 Expr
e2) = (Expr -> String
print' Expr
e1) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" + " String -> ShowS
forall a. [a] -> [a] -> [a]
++ (Expr -> String
print' Expr
e2)
print' (Mul Expr
e1 Expr
e2) = (Expr -> String
print' Expr
e1) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" * " String -> ShowS
forall a. [a] -> [a] -> [a]
++ (Expr -> String
print' Expr
e2)
print' (Neg Expr
e) = String
"-(" String -> ShowS
forall a. [a] -> [a] -> [a]
++ (Expr -> String
print' Expr
e) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

-- Final / Tagless Final
-- grammar
-- | If the grammar is not initial, it's either final or tagless final. Tagless - because it doesnt have tags (like lit, add and so on). FInal - because it is the final function. Classes are used to achieve this. Classes define what we can do with something and not how to construct a value. For example, the lit function takes an integer and returns its representation.
--
class Expression repr where
  lit :: Integer -> repr
  add :: repr -> repr -> repr

-- eval
-- | The interpretor for the grammar.
--
instance Expression Integer where
  lit :: Integer -> Integer
  lit :: Integer -> Integer
lit Integer
i = Integer
i
  add :: Integer -> Integer -> Integer
  add :: Integer -> Integer -> Integer
add Integer
a Integer
b = Integer
a Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
b

-- pretty print
-- | You don't need to cover every instance for printing, it all depends on what you need for your project/business. lit = show, because lit a = show a is redundant since they both have the same signature.
--
instance Expression String where
  lit :: Integer -> String
  lit :: Integer -> String
lit = Integer -> String
forall a. Show a => a -> String
show
  add :: String -> String -> String
  add :: String -> ShowS
add String
a String
b = String
a String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" + " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
b

-- | A new class is used to add multiplication.
--
class ExpressionMul repr where
  mul :: repr -> repr -> repr

instance ExpressionMul Integer where
  mul :: Integer -> Integer -> Integer
  mul :: Integer -> Integer -> Integer
mul Integer
a Integer
b = Integer
a Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
b

-- | Recreating prog1, just without negation; later we add multiplication.
--
-- >>> prog2
-- 90
prog2 :: Integer
prog2 :: Integer
prog2 = Integer -> Integer -> Integer
forall repr. ExpressionMul repr => repr -> repr -> repr
mul (Integer -> Integer
forall repr. Expression repr => Integer -> repr
lit Integer
6) (Integer -> Integer -> Integer
forall repr. Expression repr => repr -> repr -> repr
add (Integer -> Integer
forall repr. Expression repr => Integer -> repr
lit Integer
5) (Integer -> Integer -> Integer
forall repr. Expression repr => repr -> repr -> repr
add (Integer -> Integer
forall repr. Expression repr => Integer -> repr
lit Integer
4) (Integer -> Integer
forall repr. Expression repr => Integer -> repr
lit Integer
6)))

-- | Returns a string of the whole expression.
-- >>> prog3
-- "5 + 4 + 6"
prog3 :: String
prog3 :: String
prog3 = String -> ShowS
forall repr. Expression repr => repr -> repr -> repr
add (Integer -> String
forall repr. Expression repr => Integer -> repr
lit Integer
5) (String -> ShowS
forall repr. Expression repr => repr -> repr -> repr
add (Integer -> String
forall repr. Expression repr => Integer -> repr
lit Integer
4) (Integer -> String
forall repr. Expression repr => Integer -> repr
lit Integer
6))

--- GADT
-- | This solves the issue with types by using multiple constructors.
--
data GExpr a where
    GInt :: Integer -> GExpr Integer
    GBool :: Bool -> GExpr Bool
    GAdd :: GExpr Integer -> GExpr Integer -> GExpr Integer
    GEq :: Eq a => GExpr a -> GExpr a -> GExpr Bool

-- | You can't add GItn and GBool since they are of different types.
--
-- >>> evalG (GInt 5)
-- 5
-- >>> evalG (GAdd (GInt 5) (GInt 6))
-- 11
-- >>> evalG (GEq (GInt 5) (GInt 6))
-- False
-- >>> evalG (GAdd (GInt 5) (GBool False))
-- Couldn't match type `Bool' with `Integer'
-- Expected: GExpr Integer
--   Actual: GExpr Bool
-- In the second argument of `GAdd', namely `(GBool False)'
-- In the first argument of `evalG', namely
--   `(GAdd (GInt 5) (GBool False))'
-- In the expression: evalG (GAdd (GInt 5) (GBool False))
evalG :: GExpr a -> a
evalG :: forall a. GExpr a -> a
evalG (GInt Integer
i) = a
Integer
i
evalG (GBool Bool
b) = a
Bool
b
evalG (GAdd GExpr Integer
e1 GExpr Integer
e2) = GExpr a -> a
forall a. GExpr a -> a
evalG GExpr a
GExpr Integer
e1 a -> a -> a
forall a. Num a => a -> a -> a
+ GExpr a -> a
forall a. GExpr a -> a
evalG GExpr a
GExpr Integer
e2
evalG (GEq GExpr a
e1 GExpr a
e2) = GExpr a -> a
forall a. GExpr a -> a
evalG GExpr a
e1 a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== GExpr a -> a
forall a. GExpr a -> a
evalG GExpr a
e2