{-# 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