Haskell: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 408: Line 408:
     fmap func (Just2 a) = Just2 (func a)
     fmap func (Just2 a) = Just2 (func a)
     fmap func Nothing2 = Nothing2
     fmap func Nothing2 = Nothing2
</syntaxhighlight>
Putting it together we have
<syntaxhighlight lang="hs">
import Data.Functor
data Maybe2 a = Just2 a | Nothing2 deriving Show
instance Functor Maybe2 where
  fmap f (Just2 x) = Just2 (f x)
  fmap f Nothing2 = Nothing2
main = do
    print $ fmap (+1) (Just2 10) -- Just2 11
    print $ fmap (+2) (Nothing2) -- Nothing2
</syntaxhighlight>
</syntaxhighlight>



Revision as of 23:43, 11 February 2025

Introduction

This is my first dip into Haskell, just felt like trying it. Haskell is a purely functional programming language. rather than a declarative language. I.E. it is a declarative language rather than imperative. With declarative, you tell the language what the thing is rather than how to do the thing.

Features

Lazy

Haskell is a programming lazy language and only evaluates on usage. E.g. only evaluates the first 10

let infiniteList =  [1..]
take 10 infiniteList

Modules

Starting to look a lot like Lisp. This next bit shows how you can combine various functions of your own to produce and output

let double = x = x * 2
let increment x = x + 1
let doubleThenIncrement x = increment(double x)
doubleThenIncrement 10 -- Answer is 21

Statically Typed

With Haskell types are inferred

Hello World

Here we start with main and strange format but no curly brackets which is good.

main :: IO()
main = putStrLn "Hello World"

We build like gcc using

ghc hello.hs -o hello

Date Models

Types

  • Numbers e.g. 1
  • Chars e.g. "S"
  • Boolean e.g. True, False
  • List e.g. [1,2,3], [True, False, True]
  • Tuple e.g. (1,2,3, "string, True)

List Comprehension

You can define a function and operate against a list like this

[x*2 | x <-[1..10]] -- Answer is [2,4,6,8,10,12,14,16,18,20]

Constructs

Pattern Matching

No surprises, and I love pattern matching

-- Pattern Matching
coffeeType :: String -> String
coffeeType "Epresso" = "Strong and bold"
coffeeType "Cappuccino" = "Frothy and delicious"
coffeeType "Filter" = "Watery and weak"
coffeeType _ = "I have no idea what that is"

main = do 
    print(coffeeType "Epresso")
    print(coffeeType "Cappuccino")
    print(coffeeType "Filter")
    print(coffeeType "Latte")

Guards

Again no surprise here

bookCategory :: Int -> String
bookCategory age
    | age < 10 = "Children's book"
    | age < 18 = "Teen book"
    | otherwise = "Adult book"

main = do
    print(bookCategory 5)
    print(bookCategory 15)
    print(bookCategory 25)

Where Clause

Almost writes its self

populationDensity :: (Double, Float) -> String

populationDensity (population, area) = density where density = population / area

main = do
    print(populationDensity (100000, 1000))

Recursion

Where would we be without Factorial

-- Factorial
factorial :: Int -> Int
-- Base case
factorial 0 = 1
-- Recursive case
factorial n = n * factorial (n - 1)

main = do
    print(factorial 5)

Higher Order Functions

Higher order functions are functions that take one or more functions as arguments, or return a function as their result. In the example we had our friends from Typescript, map and filter,

let increment x = x + 1
map increment [1,2,3] -- [2,3,4]

And filter, loving the mod operation

let isEven n = n `mod` 2 == 0
filter isEven [2,3,4] -- [2,4]

Finally something new, foldl, i.e. apply operation start from position using array from left to right (lispy)

foldl (+) 0 [1,2,3,4,5] -- 15

And now first use of libraries

import Data.List (sort)
sort [10, 2, 5, 3, 6, 7, 4, 8, 9, 1]

Other functions were zipWith, takeWhile, dropWhile

-- All
all even [1,2,4] -- False
any even [1,2,4] -- True

Lambda Expressions

This is a shortcut to writing unnamed function. We do this by specifying a back slash. e.g.

print((\c -> c * 9/5 + 32) 25) -- 77.0

More On Functions

Other Built-in Functions

We have head,tail, last, init

let x = [1..10]
head x -- 1
tail x -- [2,3,4,5,6,7,8,9,10]
last x -- 10
init x -- [1,2,3,4,5,6,7,8,9]
reverse x -- [10,9,8,7,6,5,4,3,2,1]
length x -- 10
take 5 (x) -- [1,2,3,4,5]
drop 6 (x) -- [7,8,9,10] 
sum x -- 55
product x -- 3628800
elem 8(x) -- True Searches list

Function Composition

Here we can combine functions in one call. This just looks like chaining to me but here is the example like in Maths when you have f{g(x)}

-- IsEven function
isEven :: Int -> Bool
-- Is Odd function
isNotEven :: Bool -> String

isEven x = if x `rem` 2 == 0 
then True
else False

isNotEven x = if x == True
  then "This is an Even Number"
else "This is an Odd Number"

main = do
    print((isNotEven.isEven) 5)

Modules

Modules are, as ever, just functionality you can import. We looked at Data.List

main = do
  print(intersperse '|' "ILoveHaskell") -- I|L|o|v|e|H|a|s|k|e|l|l
  print(intercalate ' ' ["Haskell", "is", "great"]) -- "Haskell is great"
  print(splitAt 3 "Apple") -- ("App","le")

And the Data.Char module.

main = do
  print(toUpper 'a') -- A
  print(toLower 'B' -- b
  print(words 'FRED WAS') -- ["FRED", "WAS"]

And the Data.Map. The importing of this seemed to be a bit blurry even to the tutor

import Data.Map (Map)
import qualified Data.Map as Map

myMap :: Integer -> Map Integer [Integer ]

myMap n = Map.fromList (map makePair [1..n])
  where makePair x = (x, [x])

main = do
  print $ myMap 10

And the Data.Set

main = do
  print $ Set.fromList "Hey Jude!" -- " !HJdeuy"

Roll your Own

This seem a little hairy to recompile and make sure we are running the correct code. Possibly need to look at the tools for this

module Custom(
    showEven,
    showOdd
) where 
  showEven :: Int -> String
  showEven x = if x `mod` 2 == 0 then "Even" else "Odd"

  showOdd :: Int -> String
  showOdd x = if x `mod` 2 == 0 then "Odd" else "Even"

File IO

Reading

Very easy

main = do
    contents <- readFile "movies.txt"
    putStr contents

Writing

main = do
    let movies = ["The Godfather", "The Shawshank Redemption", "The Dark Knight", "Pulp Fiction", "The Lord of the Rings: The Return of the King"]
    writeFile "movies.txt" (unlines movies)
    putStrLn "Movies written to movies.txt"

Exception Handling

Here is an example of catching a division by zero in Haskell

import Control.Exception

main = do
  result <- try (evaluate (div 5 0)) :: IO (Either SomeException Int)
  case result of
    Left ex -> putStrLn $ "Caught exception: " ++ show ex
    Right val -> putStrLn $ "The result was: " ++ show val

Functors

Introduction

The definition I got from C++ is function object is a function with state.

Simple C++ Example

struct Value {
   int m_result {0};
   
   int operator()(int newResult) {
       m_result = newResult;
       return m_result;
   }
}

int main() {
  Value v;
  v(42);
  std::cout << v.m_result << std::endl;
}

Better C++ Example

They went on to provide a second C++ example where the Comparator was used as a good use of a functor. Calling the function Comparator with the state of the goblins.

struct Goblin {
    int health;
    int strength;
    Goblin(int health, int strength) : health(health), strength(strength) {};

    bool operator<(const Goblin& other) const {
        return health < other.health;
    }
};

struct GoblinComparator {
    bool operator()(const Goblin& a, const Goblin& b) const {
        return a.strength < b.strength;
    }
};

int main() {
    std::vector<Goblin> goblins {
        {5, 25},
        {3, 25},
        {100, 1}
    };

    std::sort(goblins.begin(), goblins.end(), GoblinComparator());

    for(const auto& goblin : goblins) {
        std::cout << "Health: " << goblin.health << " Strength: " << goblin.strength << std::endl;
    }

    return 0;
}

// Health: 100 Strength: 1
// Health: 5 Strength: 25
// Health: 3 Strength: 25

Much Better Example

Googling to make sure I understood the idea of a functor, found a much better example. which was a logger and all makes sense now.

class Logger {
public:
    Logger(const std::string& fileName) {
        m_output.exceptions(m_output.badbit | m_output.failbit);
        try
        {
            m_output.open(fileName, std::ofstream::out);
            m_output << "Logger started" << std::endl;
        }
        catch (const std::ofstream::failure& e)
        {
            std::cerr << "Failure: " << e.what() << std::endl;
            std::string error = "Could not open file: " + fileName;
            std::cout << error << std::endl;
            throw std::runtime_error(error);
        }

    }

    void operator() (const std::string& message) {
        m_output << message << std::endl;
    }

    public:
    ~Logger() {
        m_output.flush();
        m_output.close();
    }

private:
    std::ofstream m_output;
};

int main() {
    Logger logger("log.txt");
    logger("Hello, world!");
    return 0;
}

Back to Haskell

Arggghhhhh

To I am still struggling but this is what the definition of a functor is and therefore some kind of type definition, i.e. anything which looks like this is a functor

class Functor f where
    fmap :: (a->b) -> f a -> f b

The fmap definition is I guess

(a->b) -> f a -> f b

This was explained as
fmap takes a function which is of type a and results in a type b
It applies f of a and gives you a f of b

Map and FMap

So some progress, the tutor showed a couple of things which start to make more sense in the interactive session. They the types which I knew but here to keep context

:t False -- Bool
:t "hello" -- String
:t 'T' -- Char
:t 12  -- Num a=>a (an instance of typeclass Num not type)
-- Now lets look at our Maybe (like Option but instead on Some and None, Just and Nothing)
:t Nothing -- Maybe a
:t Just 5 -- Num a => Maybe a

It is the last one where thing are starting to make sense, the output suggests you put a in and you get a out. So we can now use the kind or k to look a the type constructors

:k Bool -- :: *
:k String -- :: *
:k Char -- :: *
:k Int -- :: *
-- Now lets look at our Maybe
:k Maybe -- * -> * Which means it is a function converting from type to type

So looking a map

-- Example
map (+2) [1,2,3] -- [3,4,5]

-- Looking at type
:t map -- map :: (a -> b) -> [a] -> [b]

This looks a lot like the type of fmap. fmap came first but because lists are so useful they made a specific implementation of fmap called map for lists.
You can use fmap for any shape of data but you have to tell it how to traverse the data you are using. In the example you talked about trees.

Third Video

So opening line was great, a functor is a TYPECLASS. A typeclass, you can think of as an interface you need to implement.

class Functor f where
    fmap :: (a->b) -> f a -> f b

As Oliver Hardy might say, now we are getting somewhere. The video went on to distinguish between types where are wrapped and unwrapped. They eluded to the Maybe type which can be a Just or Nothing. They demonstrated unwrapped values can be evaluated

 
(+3) 9 -- 12 Where 9 is unwrapped
-- but
(+3) (Just 9) -- Will not work

Now the Functor for some types are already defined including, Maybe, Either and List so to demonstrate we can make our on type, a copy of Maybe using this

 
data Maybe2 a = Just2 a | Nothing2 deriving Show

Now we can make our own Functor for Maybe2

 
instance Functor Maybe2 where 
    fmap func (Just2 a) = Just2 (func a)
    fmap func Nothing2 = Nothing2

Putting it together we have

 

import Data.Functor

data Maybe2 a = Just2 a | Nothing2 deriving Show

instance Functor Maybe2 where
  fmap f (Just2 x) = Just2 (f x)
  fmap f Nothing2 = Nothing2

main = do
    print $ fmap (+1) (Just2 10) -- Just2 11
    print $ fmap (+2) (Nothing2) -- Nothing2

fmap

For lists fmap and map work the same way

main = do
    print(map  (subtract 1 ) [1,2,3]) -- [0,1,2]
    print(fmap (subtract 1 ) [1,2,3]) -- [0,1,2]

Monads

So this is the Monad bit which I what I started on to learn this. Like Functors, the explanation may have been written in greek. I have no idea what he meant

class Monad m where
    return :: a -> m a
    -- Bind
    (>>=) :: m a -> (a -> m b) -> m b
    -- Then
    (>>) :: m a -> m b -> m b 
    x >> y = x >>= \_ -> y
    -- Fail
    fail :: String => m a
    fail msg = error msg

He preceded to speak about 3 laws of Monads

--- Three Laws of Monads:

--- 1 Left Identity:
return a >> = f
--- the same as : f a

-- 2 Right identity:
x >>= return
-- the same as: x  {Value not changed}


-- 3 Associativity:
(m >>= f) >>= g
m >>= (\x -> f x >>=g)

Moniods

The explanation was again useable. This is left here to fill in later