InnocentZero's Treasure Chest

HomeFeedAbout Me

09 Dec 2024

Haskell notes

Haskell notes

:{
  print (take 20 [0..23])
:}
def greeting():
    return "hello"
print(1 + 2)

The above was just a test for seeing if org-babel works.

:{
 putStrLn "Hello, everybody!"
:}

The :{:} are necessary because that's how ghci knows that they are to be executed together.

Haskell

  • First class functions.
  • No side effects.
  • Evaluate exprs rather than execute instructions.

Haskell also happens to be lazily evaluated. This means that things are only evaluated when their results are needed. This makes things like infinite data structures possible.

It's also statically typed.

  -- comment lmao 
fun () = do { let y = 3 in print (y + 3) };
  -- finally!!
fun()

Types

y =  1+1
print(y)

Haskell can infer types on its own.

This has no bounds however

let n :: Integer = 2^(2^(2^(2^(2))))
print(n)

A little caveat to take care of is the fact that not doing 3 * (-3) will probably make the compiler scream at you.

import Text.Printf
let d :: Double = 4.538799999999999
let b :: Bool = True
let c :: Char = 'ダ' -- unicode
print(d, b, c)

Also has the regular business with booleans. Inequality checked with /=

Lists in haskell

Very powerful and very helpful.

let a = [4,8,15]
print(a)

Concatenation is done with ++

print([1,2,3,4] ++ [5,6,7,8])

Strings are also lists of characters

print (['j', 'e', 'l', 'l', 'o'])
print ("jello")
  • Nil and Cons

    [] is called nil and denotes an empty list : is called cons that is a constructor for a value and another list of the same type They are enums. One thing to note here is that the constructor cons is also a binary operator so it can be applied partially like other binary operators. This is true for any data operator that starts with :.

    print ([])
    print (1 : [])
    print (1: 2 : 3 : []) -- eval'd from right to left
    
  • Concat (++)
    print ([1] ++ [2, 3])
    print (1 : [2, 3]) -- better time complexity wise
    

    The concat or ++ operator is linear in the size of the list on the left side.

  • Indexing

    You can get the n'th element of the list by using the !! operator on the list. However, check for bounds else there's a runtime error.

    print("Its a me mario" !! 4)
    -- print([1] !! 3) -- error
    
  • Other basic functions
    • head gives the first element of the list
    • tail removes the first element and returns a new list
    • last gives the last element of the list
    • init returns the list with everything except for the last element
    • length returns the length
    • null checks if the list is empty
    • reverse reverses a list
    • take takes the first k elements of the list and returns that as the new list. If it is larger than the list's length then the entire list is returned.
    • drop drops the first k elements of the list. If larger than the list's size, return an empty list.
    • maximum, minimum, sum and product do what you think they do.
    • elem takes a list and an element and returns if it was there or not. Usually written infix

    All of them fail on an empty list

    print(head [1, 2, 3, 4])
    print(tail [1, 2])
    print(last [1, 3])
    print(init [1, 3, 4])
    print(take 1 [6, 6, 6])
    print(if 4 `elem` [1, 2, 3] then "present" else "Lol")
    
  • Ranges and infinite lists
    print([1..20])
    
    -- define a step here
    print([1, 3..20])
    

    However, be wary of using it on floating point numbers because of precision. Preferably don't use them at all.

    print([0.1, 0.3 .. 1])
    

    To make an infinite list, do not specify an upper limit. Haskell won't complain unless you try to evaluate all of it.

    print(take 10 [1, 3..])
    

    cycle repeats a list forever, so you can use it to repeat something in a loop

    print(take 10 (cycle [1, 2, 3]))
    

    repeat takes an element and repeats it to give an infinite list

    print(take 10 (repeat 1))
    

    replicate takes a number and an element and creates a finite list of that argument that many times

    print(replicate 3 'a')
    
  • List Comprehensions in haskell

    Basically a method to build a list from another range/list/whatever

    print([x * 2| x <- [1..10]])
    

    Apart from this we can also specify a predicate to filter out values

    print([x | x<- [50..100], x `mod` 7 == 3])
    

    We can also use any expression in the final result that depends on the list value, such as

    print([if x < 10 then "Foo" else "bar" | x<- [1..20], odd x])
    

    We can also specify several predicates and the value has to satisfy all of them.

    We can also specify multiple lists and the comprehension will combine them in all manners.

    [x*y | x <- [2,3,5], y<- [5,7,8]]
    

Tuples in haskell

let x = (1, 3)
print(x)

They are not extensible like lists but they can contain different types of data such as a string and an int, etc. They can also be 3, 4 or even more.

If Else

They also work as expressions.

:{
doubleSmall x = if x > 100
                then x
                else x * 2
:}
doubleSmall 101

Functions in haskell

Function calls are simple, and they take precedence over anything else.

min 9 10
succ 9 * 10 -- succ 9 eval'd first
succ (9*10)

`fun` makes a function infix, as shown below. We can also define them this way!

print (16 `div` 3, 16 `rem` 3)
print (9 `max` 10)

Every operator is also an infix function.

defining functions

Functions can't begin with uppercase letters.

doubleMe x = x * 2

print(doubleMe 8.3, doubleMe 10)
:{
  doubleUs x y = doubleMe x + doubleMe y

  doubleMe x = x + x
:}
  print(doubleUs 10 11.1)

Function types

removeNonUppercase :: [Char] -> [Char]
removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]   

It has the same semantics as other functional programming languages, so multi-argument functions are basically single argument functions that return another function. This also implies partial application of functions.

Pattern matching on multiple functions

We can do a switch case style pattern matching on multiple functions as follows:

:{
    myLength :: (Num b) => [a] -> b
    myLength [] = 0
    myLength (_:xs) = 1 + myLength xs

:}

    print(myLength [1])

We can also bind a variable to patterns by using the following syntax:

:{
    capital "" = "Empty string, Whoops!"
    capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
:}

  print(capital "foo")

It also has the concept of guards, similar to match guards in Rust.

:{
  bmiTell bmi  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"  -- same as catch all

:}

  print(bmiTell 21)

We can also use the where keyword to reduce the repetition happening

:{
  bmiTell weight height  
    | bmi <= 18.5 = "You're underweight, you emo, you!"  
    | bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise   = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2  
:}

  print(bmiTell 70 1.8)

Add more context with the where keyword:

:{
  bmiTell weight height  
      | bmi <= skinny = "You're underweight, you emo, you!"  
      | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
      | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
      | otherwise     = "You're a whale, congratulations!"  
      where bmi = weight / height ^ 2  
            skinny = 18.5  
            normal = 25.0  
            fat = 30.0  
:}

  print(bmiTell 60 1.34)

You can also do pattern matching in where bindings.

Type variables

Some functions like head, fst etc have the following types

fst :: (a, b) -> a
head :: [a] -> a

Typeclasses

A typeclass defines a behaviour, and also a constraint.

(==) :: (Eq a) => a -> a -> Bool

The Eq a part is a class constraint. It basically says, it takes any two values that are of the same type and returns a Bool. Also, the two types must belong to the Eq class.

Some common typeclasses are:

  • Eq: types that have some notion of equality. Members implement both
    • (==) and
    • (/=)
  • Ord: Some notion of ordering. To be a member of this you need to be a member of Eq. Implements basically all the boolean checks. Also implements the compare function, that returns an enum Ordering which is either GT, LT or EQ.
  • Show: Shows the type as a string. Most used function is show
  • Read: Opposite of Show. read reads a string and returns the type which is inferred. If no type can be inferred then it throws an error.
  • Enum: enumerated basically. We can use list ranges, succ and pred etc. The unit type falls in this class. Also, Ordering, Int, etc.
  • Bounded: have upper and lower bounds. Important functions include minBound and maxBound. Their types are interesting because they do not take an argument, just give an output. So you need to annotate their output type.
  • Num: numeric. Basically anything that's a number.
  • Integral and Floating: obviously

A useful function to deal with numbers is fromIntegral :: (Num b, Integral a) => a -> b. We can see that the input is any integer type and the output is a generic num type.

Let bindings

Same as OCaml/Rust, scoped and act as expressions.

There are three situations of using let:

  • In the body of do (which binds to the IO monad)
:{
  do let x = 1
     x
:}
  • In list comprehensions (similar to above)
  • As an expression (let x in y) similar to OCaml

Case expressions

Same as switch case in C/match in OCaml/Rust.

:{
     head' :: [a] -> a  
     head' xs = case xs of [] -> error "No head for empty lists!"  
                          (x:_) -> x  
:}
     print(head' [1,2])

Recursion and Higher Order Functions

Recursion is what it sounds like. Write a recursive function. Since haskell is lazily evaluated, writing infinite recursion is fine. Similar to OCaml, its functions only take in one parameter. And return a function that takes in the rest of the parameters. So partial application aka currying is possible in this scenario.

Similar to OCaml, there is the provision of passing functions as arguments.

Binary operators are special. Since they take two arguments, there are syntactic sugars to resolve which operand you're binding with. This is called sectioning

applyTwice f x = f (f x)
applyTwice (++ " HAHA") "HEY"
applyTwice ("HAHA " ++) "HEY"

It also has the classic map and filter from other languages. We also have takeWhile that does what it sounds like, take while a predicate is true.

Needless to say, it also has anonymous functions/lambdas, which are written like this:

(\x -> x^2) 2

They can also have pattern matching but only of one pattern! If the pattern fails to match it's a runtime failure.

Needless to say, foldl and foldr do what they are supposed to. Unlike OCaml, they are smarter and have the same function signature. There's also foldl1 and foldr1 that automatically take the first value of the list to be the initial value.

scanl and scanr are functions that behave like the two above except they report all the intermediate accumulator states as a list. Similarly there's scanl1 and scanr1 as well.

Function application operator and composition

We've all come across the classic problem of f(g(h(x))). Rust solves this using method chaining, OCaml using pipes, and haskell using the $ operator. It can be equivalently written as f $ g $ h x.

As we have composition in mathematics, we also have composition in haskell, where \(\left(f \cdot g\right)\left(x\right)\) is written using the dot operator f . g.

Using the above two features, we can often elide the arguments to a function that's written as a combination of other functions. This is called as points free style of writing functions.

Modules

import Data.List

-- for selective imports
-- import Data.List (nub, sort)

-- for selective rejections
-- import Data.List  hiding (nub, sort)

numUniques = length . nub

numUniques [1, 1, 2, 2, 4, 5, 4]

For qualified imports,

import qualified Data.Map as M  
-- allows for usage of M.filter
Tags: programming haskell

Other posts
Creative Commons License
This website by Md Isfarul Haque is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.