Javapublic static void <T extends Comparable<T>> quickSort(T[] arr) { quickSort(arr, 0, arr.length - 1); } private static void <T extends Comparable<T>> quickSort(T[] arr, int lo, int hi) { if (lo < hi) { int p = partition(arr, lo, hi); quickSort(arr, lo, p - 1); quickSort(arr, p + 1, hi); } } private static int <T extends Comparable<T>> partition(T[] arr, int lo, int hi) { T pivot = arr[hi]; int i = lo - 1; for (int j = lo; j < hi; j++) { if (arr[j].compareTo(pivot) < 0) { swap(arr, ++i, j); } } swap(arr, i, j); }
Haskellsort :: [Integer] -> [Integer] sort [] = [] sort (pivot:xs) = (sort left) ++ [pivot] ++ (sort right) where left = filter (< pivot) xs right = filter (>= pivot) xs
Mutability.java:41: error: method solvePuzzle in class Solver cannot be applied to given types; solution = solvePuzzle(puzzle); ^ required: int[][],SolvingStrategy found: int[][] reason: actual and formal argument lists differ in length 1 error
Let's start by figuring out the type signatures of Java methods.
Javastatic int add(int lhs, int rhs) { // ... }
Haskelladd :: Integer -> Integer -> Integer
The argument types are listed in order, followed by the return type.
Javastatic double weirdAdd(float lhs, int rhs) { // ... }
HaskellweirdAdd :: Float -> Integer -> Double
Lists use a slightly different syntax.
Javastatic float average(int[] numbers) { // ... }
Haskellaverage :: [Integer] -> Float
But, strings and characters are what you would expect.
Javastatic boolean beginsWith(String search, char prefix) { // ... }
HaskellbeginsWith :: String -> Char -> Bool
What if we pass a function as an argument?
Javastatic int findNumber(int[] nums, Predicate<Integer> isWanted) { for (int num : nums) { if (isWanted.test(num)) { return num; } } throw new NumberNotFoundException(i); }
JavafindNumber(oddNumbers, num -> MathUtils.isPrime(num));
Javainterface Predicate<T> { boolean test(T t); }
JavafindNumber(oddNumbers, MathUtils::isPrime);
HaskellfindNumber :: [Integer] -> (Integer -> Bool) -> Integer
Math, boolean, and string operations are straightfoward.
Haskell> 4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + 1/13 - 1/15 + 1/17 - 1/19) 3.0418396189294032
Haskell> (True && False) || (True && True) True
Haskell> 8 /= 2 * 2 * 2 -- not equal False
Haskell> 1.0 == 1 True
Haskell> "To understand recursion " ++ "we must first understand recursion" "To understand recursion we must first understand recursion"
But you don't get implicit casting...
Haskell> "A random number: " ++ 4
<interactive>:2:24: error:
• No instance for (Num String) arising from the literal ‘4’
• In the second argument of ‘(++)’, namely ‘4’
In the expression: "A random number: " ++ 4
Haskell> "A random number: " ++ show 4 "A random number: 4"
Why didn't concatenating a string and integer work? Let's look at the types!
Haskell(++) :: String -> String -> String
Haskellshow :: Integer -> String
As an exercise, let's recall the types of some of these functions
Haskell(+) :: Integer -> Integer -> Integer (-) :: Integer -> Integer -> Integer (*) :: Integer -> Integer -> Integer (/) :: Integer -> Integer -> Integer (&&) :: Bool -> Bool -> Bool (||) :: Bool -> Bool -> Bool
In Java, we'd probably want to express factorial iteratively:
Javapublic static int factorial(int n) { int product = 1; for (int i = 1; i <= n; i++) { product *= i; } return product; }
But in Haskell, we need to think recursively:
Javapublic static int factorial(int n) { if (n == 0) { return 1; } return n * factorial(n - 1); }
Javapublic static int factorial(int n) { if (n == 0) { return 1; } return n * factorial(n - 1); }
Translated "verbatim" to Haskell, this would look like:
Haskellfactorial :: Integer -> Integer factorial n = if n == 0 then 1 else n * factorial (n - 1)
Do you see any issues with these implementations?
The bread and butter of functional programming is lists.
GHCI> ["lists", "must", "be", "homogenous"] ["lists", "must", "be", "homogenous"]
GHCI> ["so", "this", "breaks", 22]
<interactive>:5:26: error:
• No instance for (Num String) arising from the literal ‘22’
• In the expression: 22
In the expression: ["so", "this", "breaks", 22]
The above is syntactic sugar for an important function called cons:
GHCI> "cons" : "prepends" : ["to", "lists"] ["cons", "prepends", "to", "lists"]
Haskell(:) :: a -> [a] -> [a]
Haskell(:) :: a -> [a] -> [a]
This is similar to generics in Java:
Javapublic static <T> T[] cons(T item, T[] items) { // ... }
So, a
or T
can be replaced with any type.
GHCI> 1 : [2, 3] [1, 2, 3] > "one" : ["two", "three"] ["one", "two", "three"]
Javacons(1, new Integer[] {2, 3}); // {1, 2, 3} cons("uno", new String[] {"dos", "tres"}); // {"uno","dos","tres"}
Strings are just lists of characters!
GHCI> ['J','o','h','n',' ','H','o','p','k','i','n'] "John Hopkin" > 'J':'o':'h':'n':' ':'H':'o':'p':'k':'i':'n':[] "John Hopkin"
And concatenation works on generic lists, not just strings!
GHCI> [1, 2, 3] ++ [4, 5, 6] [1, 2, 3, 4, 5, 6]
And that's because the type of concatenate is actually general:
Haskell(++) :: [a] -> [a] -> [a]
Constructing lists of numbers is tedious, so we use ranges instead:
GHCI> [1..10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > [10,9..1] [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
There are lots of builtin functions for working with lists.
GHCI> length [2, 3, 4, 5, 6] 5 > intercalate ", " ["foo", "bar", "baz"] "foo, bar, baz" > concat [[1, 2, 3], [2, 4, 6]] [1, 2, 3, 2, 4, 6] > head ["foo", "bar", "baz"] "foo"
What about infinite lists?
GHCI> [1..] [1, 2, 3, 4, 5, -- forever and ever > head [1..] 1
Why doesn't the first one never finish, but the second does? Laziness
Pythondef positive_integers(): i = 1 while True: yield i i += 1 > numbers = positive_integers() > next(numbers) 1 > next(numbers) 2
But what happens if we do this:
GHCI> head [] *** Exception: Prelude.head: empty list
This is bad. It compiles, but you get a runtime error. We can do better!
Our previous implementation is bad, because it uses control structures.
Haskellfactorial :: Integer -> Integer factorial n = if n == 0 then 1 else n * factorial (n - 1)
The preferred approach when you have conditions is guard syntax:
Haskellfactorial :: Integer -> Integer factorial n | n == 0 = 1 | otherwise = n * factorial (n - 1)
This syntax also allows multiple branches:
Haskellfactorial :: Integer -> Integer factorial n | n < 0 = error "complex number" | n == 0 = 1 | otherwise = n * factorial (n - 1)
But for this there's an even better approach.
Haskellfactorial :: Integer -> Integer factorial n = if n == 0 then 1 else n * factorial (n - 1)
Pattern matching works by choosing which expression to evaluate (which definition of a function to use) based on argument patterns:
Haskellfactorial :: Integer -> Integer factorial 0 = 1 factorial n = n * factorial (n - 1)
You can use _
(hole) to indicate that you don't care about the value of an
argument (matches any value):
HaskellignoreSecond :: a -> b -> a ignoreSecond a _ = a
As an example say we wanted to write a function to sum a list of numbers:
Pythondef sum(numbers): total = 0 for number in numbers: total += number return total
We need to write this recursively to translate it to Haskell:
Pythondef sum(numbers): return numbers[0] + sum(numbers[1:])
But you may notice this has a bug!
Pythondef sum(numbers): if len(numbers) == 0: return 0 return numbers[0] + sum(numbers[1:])
Pythondef sum(numbers): if len(numbers) == 0: return 0 return numbers[0] + sum(numbers[1:])
With a correct Python implementation, we can see a clear path to the Haskell version:
Haskellsum :: [Integer] -> Integer sum [] = 0 sum (x:xs) = x + sum xs
Haskellsum :: [Integer] -> Integer sum [] = 0 sum (x:xs) = x + sum xs
Cons can be used in pattern matching to destructure lists:
Haskellsum [1, 2, 3] = sum (1 : [2, 3]) = 1 + sum [2, 3] = 1 + sum (2 : [3]) = 1 + 2 + sum [3] = 1 + 2 + sum (3 : []) = 1 + 2 + 3 + sum [] = 1 + 2 + 3 + 0
This is a useful pattern! We can use it for other operations on lists.
We can also build a function to compute the product of a list of numbers.
Haskellsum :: [Integer] -> Integer sum [] = 0 sum (x:xs) = x + sum xs
Haskellproduct :: [Integer] -> Integer product [] = 1 product (x:xs) = x * product xs
These work as you would expect:
GHCI> sum [1, 3, 5] 8 > product [1, 3, 5] 15
But wait, isn't n!
the product of all integers from 1
to
n
?
So, when we originally had:
Haskellfactorial :: Integer -> Integer factorial n = 1 factorial n = n * factorial (n - 1)
We can combine our product
and ranges to arrive at:
Haskellfactorial :: Integer -> Integer factorial n = product [1..n]
Prefer small functions that do one thing well. For more complex tasks, compose these small functions together. This makes you code more easily testable and more obviously correct.
Also known as the single resposibility principle.
Returning our list operations, we should notice a pattern:
Haskellsum :: [Integer] -> Integer sum [] = 0 sum (x:xs) = x + sum xs product :: [Integer] -> Integer product [] = 1 product (x:xs) = x * product xs train :: Model -> Example -> Model emptyModel :: Model -- Leave it to the pros... machineLearn :: [Example] -> Model machineLearn [] = emptyModel machineLearn (x:xs) = train (machineLearn xs) x
All of these have some default value for the base case (empty list), and apply a function recursively on this list until that base case is reached. There is an opportunity to refactor!
Haskellfoldl :: (a -> b -> a) -> a -> [b] -> a foldl _ initial [] = initial foldl action initial (x:xs) = action (foldl action initial xs) x
The works exactly how our sum
did!
Haskellsum [1, 2] = sum (1 : [2]) = 1 + sum [2] = 1 + sum (2 : []) = 1 + 2 + sum [] = 1 + 2 + 0
Haskellfoldl f initial [1, 2] = foldl f initial (1 : [2]) = 1 `f` (foldl f initial [2]) = 1 `f` (foldl f initial (2 : [])) = 1 `f` 2 `f` (foldl f initial []) = 1 `f` 2 `f` initial
Haskellfoldl :: (a -> b -> a) -> a -> [b] -> a
With this behavior extracted, we can rewrite our other functions:
Haskellsum :: [Integer] -> Integer sum xs = foldl (+) 0 xs product :: [Integer] -> Integer product xs = foldl (*) 1 xs train :: Model -> Example -> Model emptyModel :: Model -- Leave it to the pros... machineLearn :: [Example] -> Model machineLearn examples = foldl train emptyModel examples
Haskellsum xs = foldl (+) 0 xs product xs = foldl (*) 1 xs machineLearn examples = foldl train emptyModel examples
This is a worthwhile refactoring, because if for example, we test foldl
and
train
and are sure that they are correct, then we know that
machineLearn
is also correct!
Extract common behavior between functions into a small, composable function to make your functions easier to reason about and more obviously correct.
It turns out there are other functions like this. What if we wanted a list of prime numbers?
One (naive) way to obtain this is to go through a list of all positive integers and pull out the ones that are prime.
Haskell-- Assuming we had a: isPrime :: Integer -> Bool primes :: [Integer] primes = filter isPrime [2..]
We could do this with a function like:
Haskellfilter (a -> Bool) -> [a] -> [a] filter predicate xs = foldl collect [] xs where collect :: [a] -> a -> [a] collect collected item | predicate item = item:collected | otherwise = collected
Haskellfoldl :: (c -> d -> c) -> c -> [d] -> c
What if we had a list of videos that we needed to re-encode for different clients (different qualities and sizes for mobile devices, for example) for a video CDN? We have a function that does the re-encoding and a list of videos.
HaskellencodeVideo :: Video -> [EncodedVideo] encodeVideos :: [Video] -> [[EncodedVideo]] encodeVideos videos = map encodeVideo videos map :: (a -> b) -> [a] -> [b] map f (x:xs) = f x : map f xs
See the mistake? Haskell does!
Haskell<interactive>:2:1: warning: [-Wincomplete-patterns]
Pattern match(es) are non-exhaustive
In an equation for ‘map’: Patterns not matched: _ []
Haskellmap (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs
Use enums and inversion of control to have the compiler check that you have implemented all cases. In switch statements, the compilers can usually point out missed cases. In other cases, instead of code like this:
Javaif (entry instanceof Book) { return ((Book) entry).getIsbn(); } else if (entry instanceof Movie) { return ((Movie) entry).getBarcode(); }
Prefer code like this:
Javainterface Identifiable { public String getIdentifier(); } class Book implements Identifiable { public String getIdentifier() { return this.getIsbn(); } } class Movie implements Identifiable { public String getIdentifier() { return this.getBarcode(); } } // And then use it like... return entry.getIdentifier();
What if we want to consider pairs of items from two lists at the same time?
Haskellzip :: [a] -> [b] -> [(a, b)] zip (x:xs) (y:ys) = (x, y) : zip xs ys zip _ _ = []
GHCI> zip ["Compilers", "PL", "Parallel"] [62, 95, 89] [("Compilers", 62), ("PL", 95), ("Parallel", 89)]
This family of functions leads us to our next tip:
Prefer lazy functional tools like map
, filter
,
zip
. They don't construct lists in memory, so they're often more
efficient.
Python has functions of this name. In Java 8, look at the Stream API.
As an example, instead of doing:
Pythoncourses = ["Compilers", "PL", "Parallel"] grades = [62, 95, 89] for i, course in enumerate(courses): print(f'I got a {grades[i]} in {course}!')
Prefer this:
Pythoncourses = ["Compilers", "PL", "Parallel"] grades = [62, 95, 89] for course, grade in zip(courses, grades): print(f'I got a {grade} in {course}!')
Because there are no explicit indexes, it's hard to pair two things incorrectly. And
you'll also never run into any IndexError
s or
ArrayOutOfBoundsException
s!
In Haskell, a function always takes one parameter and returns some value.
Wait, what?
Haskell(+) :: Integer -> (Integer -> Integer) (1 +) :: Integer -> Integer (1 + 1) :: Integer
This means we can do:
GHCI> map (2 *) [1..5] [2, 4, 6, 8, 10]
Can you implement quicksort correctly from memory? Do you get all of the bounds correct? How about the termination conditions?
Javapublic static void <T extends Comparable<T>> quickSort(T[] arr) { quickSort(arr, 0, arr.length - 1); } private static void <T extends Comparable<T>> quickSort(T[] arr, int lo, int hi) { if (lo < hi) { int p = partition(arr, lo, hi); quickSort(arr, lo, p - 1); quickSort(arr, p + 1, hi); } } private static int <T extends Comparable<T>> partition(T[] arr, int lo, int hi) { T pivot = arr[hi]; int i = lo - 1; for (int j = lo; j < hi; j++) { if (arr[j].compareTo(pivot) < 0) { swap(arr, ++i, j); } } swap(arr, i, j); }
Wouldn't you rather write?
Haskellsort :: [Integer] -> [Integer] sort [] = [] sort (pivot:xs) = (sort left) ++ [pivot] ++ (sort right) where left = filter (< pivot) xs right = filter (>= pivot) xs
What if we wanted to make a quicksort work for lists of anything that can be ordered?
Haskellsort :: [a] -> [a]
sort [] = []
sort (pivot:xs) = (sort left) ++ [pivot] ++ (sort right)
where left = filter (< pivot) xs
right = filter (>= pivot) xs
<interactive>:16:24: error:
• No instance for (Ord a) arising from a use of ‘<’
Possible fix:
add (Ord a) to the context of the type signature for:
sort :: forall a. [a] -> [a]
• In the first argument of ‘filter’, namely ‘(< pivot)’
In the expression: filter (< pivot) xs
In an equation for ‘left’: left = filter (< pivot) xs
<interactive>:17:25: error:
• No instance for (Ord a) arising from a use of ‘>=’
Possible fix:
add (Ord a) to the context of the type signature for:
sort :: forall a. [a] -> [a]
• In the first argument of ‘filter’, namely ‘(>= pivot)’
In the expression: filter (>= pivot) xs
In an equation for ‘right’: right = filter (>= pivot) xs
So let's take its advice!
Haskellsort :: Ord a => [a] -> [a] sort [] = [] sort (pivot:xs) = (sort left) ++ [pivot] ++ (sort right) where left = filter (< pivot) xs right = filter (>= pivot) xs
So why did this work?
Haskellclass Ord a where compare :: a -> a -> Ordering -- either LT, EQ, or GT
Other interesting typeclasses:
Haskellclass Eq a where (==) :: a -> a -> Bool class Show a where show :: a -> String class Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a
How do we represent data?
Javapublic class Product { public String name; public float cost; public Product(String name, float cost) { this.name = name; this.cost = cost; // NOTE: NEVER use floats for currency } public int hashCode() { return this.name.hashCode(); } public boolean equals(Object other) { if (other instanceof Product) { return this.name == ((Product) other).name; } return false; } public String toString() { return "Product: " + this.name + " - Cost: $" + this.cost; } }
What could go wrong with that Java code?
JavaHashMap<String, Product> products = new HashMap<>(); Product slapChop = new Product("Slap Chop", 4.95f); products.put(slapChop.name, slapChop); System.out.println(products.get("Slap Chop")); // Product: Slap Chop - Cost: $4.95 // They decided to rebrand slapChop.name = "Slap Chop!"; System.out.println(products.get("Slap Chop!")); // null
Because Product
was mutable, it was hard to see this problem.
Haskell has a simple answer. Data is immutable by default. If you want to change data, you need to reconstruct it.
Haskelldata Product = Product String Float describeProduct :: Product -> String describeProduct (Product name cost) = "Product: " ++ name ++ " - Costs: $" ++ (show cost) recommend :: Product -> String recommend (Product name _) = "Other customers also bought: " ++ name discount :: Product -> Product discount (Product name cost) = Product name (cost / 2)
Prefer immutable objects where possible. It is easier to reason about things that don't change.
There are also other advantages to Haskell's approach. Consider:
Javapublic class Cart { protected List<Product> products; public boolean isEmpty() { return this.products.size() == 0; } }
Haskelldata Cart = Cart [Product] isEmpty :: Cart -> Bool isEmpty (Cart []) = True isEmpty _ = False
Every value is not nullable in Haskell!
Avoid null
where possible! Use static analysis tools to ensure that
values you expect to be non-null aren't actually null.
But what if we want a nullable value?
HaskelllookupBarcode :: String -> Product lookupBarcode = -- How do we handle failure?
Haskelldata Maybe a = Just a | Nothing
HaskelllookupBarcode :: String -> Maybe Product lookupBarcode "01234" = Just $ Product "Tide Pods" 4.95f lookupBarcode _ = Nothing
GHCI> lookupBarcode "01234" Just (Product "Tide Pods" 4.95f) > lookupBarcode "45678" Nothing
Haskell($) : (a -> b) -> a -> b
But then how do we compose functions that return a Maybe
?
This won't work:
Haskellrecommend :: Product -> String
lookupBarcode :: String -> Maybe Product
recommendBarcode :: String -> String
recommendBarcode barcode = recommend $ lookupBarcode barcode
<interactive>:26:40: error:
• Couldn't match expected type ‘Product’
with actual type ‘Maybe a0’
• In the second argument of ‘($)’, namely ‘lookupBarcode barcode’
In the expression: recommend $ lookupBarcode barcode
In an equation for ‘recommendBarcode’:
recommendBarcode barcode = recommend $ lookupBarcode barcode
To do this we'll take advantage of a typeclass called Functor
.
Haskellclass Functor f where (<$>) :: (a -> b) -> f a -> f b
Doesn't this look familiar?
Haskellmap :: (a -> b) -> [a] -> [b]
For Maybe
it is defined like so:
Haskellinstance Functor Maybe where f <$> (Just x) = Just $ f x _ <$> Nothing = Nothing
We can take advantage of this and write:
Haskellrecommend :: Product -> String lookupBarcode :: String -> Maybe Product recommendBarcode :: String -> Maybe String recommendBarcode barcode = recommend <$> lookupBarcode barcode
Learn You a Haskell for Great Good
Hoogle - for when you need a function
/