{-# OPTIONS_GHC -Wno-missing-export-lists #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Redundant lambda" #-}
-- | Notes taken by Ugnė Pacevičiūtė 
module Lessons.Lesson04 where

import Data.Char (isAlpha, isDigit)
import Data.List (isPrefixOf)
import Control.Concurrent (Chan)


-- | The 'add' function takes two integers and returns their sum.
-- It is a simple example of a named function definition.
add :: Int -> Int -> Int
add :: Int -> Int -> Int
add Int
a Int
b = Int
a Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
b

-- | Anonymous functions (and one of their implementations - lambda functions) are functions without a name.
-- In Haskell, they are written using a backslash `\`, followed by their parameters.
--
-- The function below demonstrates how 'foldl' can be used with both named and anonymous functions.
-- 'foldl' stands for fold left, meaning it combines all elements of a list using a binary function.
--
-- >>> foldl add 0 [1,2,3,4,5]
-- 15
--
-- >>> foldl add 0 []
-- 0
--
-- >>> foldl add (-1) []
-- -1
--
-- >>> foldl (\a b -> a * b) 1 [1,2,3,4]
-- 24
sumOfInts :: [Int] -> Int
sumOfInts :: [Int] -> Int
sumOfInts [] = Int
0
sumOfInts (Int
h:[Int]
t) = Int
h Int -> Int -> Int
forall a. Num a => a -> a -> a
+ [Int] -> Int
sumOfInts [Int]
t

-- | In this example, 't' represents the tail of a list — that is, all elements except the first.
-- This function shows how to apply a given function 'f' to every element in the list.
-- 
-- It is essentially a reimplementation of Haskell’s built-in 'map' function.

-- >>> map length ["labas", "medi"]
-- [5,4]
mapping :: (a -> b) -> [a] -> [b]
mapping :: forall a b. (a -> b) -> [a] -> [b]
mapping a -> b
_ [] = []
mapping a -> b
f (a
h:[a]
t) = a -> b
f a
h b -> [b] -> [b]
forall a. a -> [a] -> [a]
: (a -> b) -> [a] -> [b]
forall a b. (a -> b) -> [a] -> [b]
mapping a -> b
f [a]
t

-- | The 'Either' type is used to represent computations that can return one of two possible results — typically an error (Left) or a success (Right).
--
-- This function safely performs integer division. If the divisor is zero, it returns an error message wrapped in 'Left'. Otherwise, it returns the result in 'Right'.
--
-- >>> safeDivision 1 0
-- Left "Division by zero"
--
-- >>> safeDivision 1 10
-- Right 0
safeDivision :: Integer -> Integer -> Either String Integer
safeDivision :: Integer -> Integer -> Either String Integer
safeDivision Integer
_ Integer
0 = String -> Either String Integer
forall a b. a -> Either a b
Left String
"Division by zero"
safeDivision Integer
a Integer
b = Integer -> Either String Integer
forall a b. b -> Either a b
Right (Integer
a Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
b)

-- | The type 'Parser a' represents a function that takes a string and tries to extract a value of type 'a' from its beginning.
-- It returns either:
--   'Left' with an error message, or
--   'Right' with a tuple ('a', 'String') — the parsed value and the remaining input.
type ErrorMsg = String
type Parser a = String -> Either ErrorMsg (a, String)

-- | This parser tries to read a single alphabetic letter from the beginning of a string.
--
-- If the input is empty, it fails with an error message.
-- If the first character is a letter, it returns it along with the rest of the string.
-- Otherwise, it returns an error describing what went wrong.
--
-- The operator '$' allows us to avoid parentheses for function application.
--
-- >>> parseLetter "fksdjhfdjk"
-- Right ('f',"ksdjhfdjk")
--
-- >>> parseLetter ""
-- Left "A letter is expected but got empty input"
--
-- >>> parseLetter "3213fksdjhfdjk"
-- Left "A letter is expected, but got 3"
parseLetter :: Parser Char
parseLetter :: Parser Char
parseLetter [] = Parser Char
forall a b. a -> Either a b
Left String
"A letter is expected but got empty input"
parseLetter (Char
h:String
t) =
  if Char -> Bool
isAlpha Char
h
    then (Char, String) -> Either String (Char, String)
forall a b. b -> Either a b
Right (Char
h, String
t)
    else Parser Char
forall a b. a -> Either a b
Left Parser Char -> Parser Char
forall a b. (a -> b) -> a -> b
$ String
"A letter is expected, but got " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
h]

-- | This parser works similarly to 'parseLetter', but it checks for digits.
--
-- >>> parseDigit "ghfjkd"
-- Left "A digit is expected, but got g"
--
-- >>> parseDigit "55ghfjkd"
-- Right ('5',"5ghfjkd")
parseDigit :: Parser Char
parseDigit :: Parser Char
parseDigit [] = Parser Char
forall a b. a -> Either a b
Left String
"A digit is expected but got empty input"
parseDigit (Char
h:String
t) =
  if Char -> Bool
isDigit Char
h
    then (Char, String) -> Either String (Char, String)
forall a b. b -> Either a b
Right (Char
h, String
t)
    else Parser Char
forall a b. a -> Either a b
Left Parser Char -> Parser Char
forall a b. (a -> b) -> a -> b
$ String
"A digit is expected, but got " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
h]

-- | The 'many' parser runs another parser repeatedly on the input.
-- It succeeds even if the inner parser matches zero times.
-- 
-- The helper function 'many'' carries an accumulator 'acc' that collects results.
-- 
-- 'v' represents the value returned by the inner parser and 'r' represents the remaining input string after parsing.
many :: Parser a -> Parser [a]
many :: forall a. Parser a -> Parser [a]
many Parser a
p = Parser a -> [a] -> String -> Either String ([a], String)
forall {t} {a} {a} {a}.
(t -> Either a (a, t)) -> [a] -> t -> Either a ([a], t)
many' Parser a
p []
  where
    many' :: (t -> Either a (a, t)) -> [a] -> t -> Either a ([a], t)
many' t -> Either a (a, t)
p' [a]
acc = \t
input ->
      case t -> Either a (a, t)
p' t
input of
        Left a
_ -> ([a], t) -> Either a ([a], t)
forall a b. b -> Either a b
Right ([a]
acc, t
input)
        Right (a
v, t
r) -> (t -> Either a (a, t)) -> [a] -> t -> Either a ([a], t)
many' t -> Either a (a, t)
p' ([a]
acc [a] -> [a] -> [a]
forall a. [a] -> [a] -> [a]
++ [a
v]) t
r

-- | The 'many1' parser behaves like 'many', but it requires at least one successful parse.
-- If the input does not contain any valid elements, it fails.
--
-- >>> parseString ""
-- Left "At least on value required"
-- >>> parseString "afds"
-- Right ("afds","")
-- >>> parseString "afds5345"
-- Right ("afds","5345")
many1 :: Parser a -> Parser [a]
many1 :: forall a. Parser a -> Parser [a]
many1 Parser a
p = \String
input ->
  case Parser a -> String -> Either String ([a], String)
forall a. Parser a -> Parser [a]
many Parser a
p String
input of
    Left String
e -> String -> Either String ([a], String)
forall a b. a -> Either a b
Left String
e
    Right ([], String
_) -> String -> Either String ([a], String)
forall a b. a -> Either a b
Left String
"At least on value required"
    Right ([a], String)
a -> ([a], String) -> Either String ([a], String)
forall a b. b -> Either a b
Right ([a], String)
a

-- | This parser combines everything we have built so far.
-- It parses one or more letters from the beginning of the input string.
-- It succeeds if at least one letter is found, and returns the collected string.
--
-- >>> parseString "afds"
-- Right ("afds","")
--
-- >>> parseString "afds5345"
-- Right ("afds","5345")
--
-- >>> parseString "afds 5345"
-- Right ("afds"," 5345")
parseString :: Parser String
parseString :: Parser String
parseString = Parser Char -> Parser String
forall a. Parser a -> Parser [a]
many1 Parser Char
parseLetter

-- | The 'pmap' function allows us to transform the result produced by a parser without changing how that parser actually reads the input string.
--
-- A parser of type 'Parser a' returns either:
--   'Left' with an error message if parsing fails, or
--   'Right (v, r)' where 'v' is the parsed value and 'r' is the remaining input.
--
-- 'pmap' takes a transformation function @f :: a -> b@ and a parser @p :: Parser a@,
-- and produces a new parser @Parser b@ that behaves just like @p@ but applies @f@
-- to the successfully parsed value before returning it.
--
-- The overall structure of the result ('Left' or 'Right', plus the remaining input)
-- stays exactly the same — only the parsed value itself is modified.
--
-- For example, if @many1 parseDigit@ returns @Right ("123", "")@,
-- then @pmap read (many1 parseDigit)@ will return @Right (123, "")@.
--
-- This pattern is the same idea as 'map' for Lists: map over the value
-- inside a container (in this case, the parsing context).
pmap :: (a -> b) -> Parser a -> Parser b
pmap :: forall a b. (a -> b) -> Parser a -> Parser b
pmap a -> b
f Parser a
p = \String
input ->
  case Parser a
p String
input of
    Left String
e -> String -> Either String (b, String)
forall a b. a -> Either a b
Left String
e
    Right (a
v, String
r) -> (b, String) -> Either String (b, String)
forall a b. b -> Either a b
Right (a -> b
f a
v, String
r)


-- | This parser reads one or more digits from the input string and converts them into an 'Integer'.
-- It demonstrates how to combine parsing ('many1 parseDigit') with transformation ('pmap read').
--
-- >>> parseInteger "3423432gfhg"
-- Right (3423432,"gfhg")
parseInteger :: Parser Integer
parseInteger :: Parser Integer
parseInteger = (String -> Integer) -> Parser String -> Parser Integer
forall a b. (a -> b) -> Parser a -> Parser b
pmap String -> Integer
forall a. Read a => String -> a
read (Parser String -> Parser Integer)
-> Parser String -> Parser Integer
forall a b. (a -> b) -> a -> b
$ Parser Char -> Parser String
forall a. Parser a -> Parser [a]
many1 Parser Char
parseDigit