Mercurial > repos > other > SevenLanguagesInSevenWeeks
changeset 87:2b5341fc4555
Add Haskell Day 1 code and notes
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 15 Jun 2019 17:31:22 +0100 |
parents | ab08b4bcd4a9 |
children | d6855f9d7eae |
files | 7-Haskell/README.txt 7-Haskell/all_even.hs 7-Haskell/composition.hs 7-Haskell/day1.hs 7-Haskell/factorial.hs 7-Haskell/fibonacci.hs |
diffstat | 6 files changed, 128 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/README.txt Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,35 @@ +Install with `zypper install ghc` for the Glasgow Haskell Compiler. The interactive shell (REPL) is `ghci`. Currently on v8.6.5 (was 6.12.1 in the book). Haskell uses strong static types with inference. + +Numbers behave like numbers. Strings have double-quotes and characters have single quotes. An array of characters (in square brackets) is a string. "+" is purely numeric addition - concatenation is "++". + +Equality is tested with "==" (equals) and "/=" (not equals - not "is divisible by"!). Haskell is strongly typed + +Indentation is significant in Haskell (like Python) BUT you can do an "if … then … else …" on a single line (however, it will assume that "if then" is a parse error, probably because functional code isn't imperative and so can't miss the else) + +Using functions on the wrong type of arguments seems to give helpful error messages along the lines of "No instance for (arg types) arising from a use of 'function' at X" (and "+" is a function). +":t val" lets you see the type of a variable or value, and ":set +t" lets you see the type of returned values. + +In code, functions are defined as "type function_name param = body". In code, that is always prefixed with "let". +Functions return the result of their last instruction. +Full function definitions are preceded by a type definition line in the form "function_name :: type(s) -> type(s)". You can also have generics and have "function_name :: (Parent_class generic_name) => generic_name -> generic_name". +Code in files needs to be in a module, which starts "module ModuleName where" on the top line. +You can then do `:load filename.hs` in the console. This then puts you in that module on the CLI (the prompt changes). + +Haskell functions can use parameter matching on arguments - see factorial.hs. + +Invoking parameters doesn't use brackets (because they're used for tuples - except when it's just a single value, when they're ignored). + +Lists can be broken up with "head" and "tail" functions (or "fst" and "snd" for tuples) *or* they can be broken up on assignment. It's like Python multi-assignment, but with colon - "(_head:_tail)" (":" is the list construction operator). It can then be used in function definitions, e.g.: + size [] = 0 + size (h:t) = 1 + size t + +Haskell can create ranges with [start..end]. Specifying an invalid range (e.g. decreasing) gives an empty list. Specifying an increment is different to all other languages: [start, next..end] (e.g. "[10, 8..4]" gives "[10, 8, 6, 4]" and it works out to decrement by 2). +You can also do (lazy) infinite ranges by not specifying an end and then using "take" functions etc to pull just the values you need. + +List comprehension is slightly pythonic: "[expr | val <- [vals] ]" where "expr" is the expression to calculate in the new list, val is a variable used in expr and [vals] is the source list. It's effectively "expression for value in list". +You can also assign multiple independent variables, possibly from the same source. For example: + let crew = ["Kirk", "Spock", "McCoy"] + [(a,b) | a <- crew, b <- crew] +calculates all combinations of crew names. Changing it to: + [(a,b) | a <- crew, b <- crew, a /= b] +lets you add filtering to stop people being paired with themselves. Or you could use "a < b" to make it return unordered unique pairings. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/all_even.hs Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,16 @@ +module AllEven where + allEven :: [Integer] -> [Integer] + allEven [] = [] + -- Note: "h:allEven t" doesn't parse the same as some other languages. We're not invoking anything on "h". + -- It is literally "h" joined to the list made by "allEven t" + allEven (h:t) = if even h then h:allEven t else allEven t + + allEvenGuard :: [Integer] -> [Integer] + allEvenGuard [] = [] + -- We can't include the empty list case because decomposing an empty list with "h:t" gives the exception "Non-exhaustive patterns in h : t" + allEvenGuard (h:t) + | even h = h:allEven t + | otherwise = allEven t + + allEvenComp :: [Integer] -> [Integer] + allEvenComp lst = [x | x <- lst, even x] \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/composition.hs Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,17 @@ +module Composition where + -- Haskell doesn't have a simple "subclass of Object" generic (https://stackoverflow.com/questions/6479444/is-there-a-type-any-in-haskell) but you can just use arbitrary values and not bind them to anything! + -- The code in the book does this in the CLI and just avoids the signature definition + second :: [o] -> o + -- Here we use an implicit argument (so just the function to the left) which then becomes an implicit parameter to tail, which has head invokes on it due to function composition by "." (https://wiki.haskell.org/Function_composition) + second = head . tail + + -- Fibonacci - like in fibonacci.hs, but without the index + fib :: Integer -> Integer + fib = fst . fibNthPair + + fibNextPair :: (Integer, Integer) -> (Integer, Integer) + fibNextPair (x, y) = (y, x + y) + + fibNthPair :: Integer -> (Integer, Integer) + fibNthPair 1 = (1, 1) + fibNthPair n = fibNextPair (fibNthPair (n - 1)) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/day1.hs Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,19 @@ +module Day1 where + -- All even by list comprehension and guard are in all_even.hs + + my_reverse :: [a] -> [a] + my_reverse [] = [] + my_reverse (h:t) = reverse t ++ [h] + + colour_combo :: [String] -> [(String, String)] + colour_combo colours = [(colour_1, colour_2) | colour_1 <- colours, colour_2 <- colours, colour_1 < colour_2] + + timestables :: [(Integer, Integer, Integer)] + timestables = [(x, y, x * y) | x <- [1..12], y <- [1..12]] + + -- Map colouring problem - we don't want adjacent places to be the same colour + colours = ["red", "green", "blue"] + map_colouring :: [(String, String, String, String, String)] + -- This just brute-forces all tuples and applies the rules, BUT it works and it uses Haskell list comprehension + map_colouring = [(al, mi, ga, tn, fl) | al <- colours, mi <- colours, ga <- colours, tn <- colours, fl <- colours, + mi /= tn, mi /= al, al /= tn, al /= mi, al /= ga, al /= fl, ga /= fl, ga /= tn] \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/factorial.hs Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,21 @@ +module Factorial where + -- The book says to use "Main", but that now triggers "The IO action ‘main’ is not defined in module ‘Main’". https://optimistictypes.com/compiler-errors/#module-main-where says Main is special and must have a function "main". + -- Instead we'll use our own module name, because it doesn't seem to be important (yet). + + -- Without pattern matching + fact :: Integer -> Integer + -- Note how this is like a Python "x if a else y" + fact x = if x == 0 then 1 else x * fact (x - 1) + + -- With pattern matching + factorial :: Integer -> Integer + factorial 0 = 1 + factorial x = x * factorial(x - 1) + + -- With guards + factorial_guard :: Integer -> Integer + factorial_guard x + -- List of definitions, which look similar to the pattern matching but without the function name + | x > 1 = x * factorial(x - 1) + -- And the fallback "otherwise" result + | otherwise = 1 \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/7-Haskell/fibonacci.hs Sat Jun 15 17:31:22 2019 +0100 @@ -0,0 +1,20 @@ +module Fibonacci where + -- Classic imperative Fibonacci + -- XXX: This seems to give odd answers! "fib_simple 0" gives 1 when it should be 0 (0-based indexing) or an error (1-based indexing)! + fib_simple :: Integer -> Integer + fib_simple 0 = 1 + fib_simple 1 = 1 + fib_simple x = fib_simple (x - 1) + fib_simple (x - 2) + + -- Fibonacci with tuples and effective caching + -- This works by using the parameter as the number of steps and iterating those in the tuple + -- Note: We're zero-indexing, so "fib 0" is the first Fibonacci number + fib :: Integer -> Integer + fib x = fibResult (fibTuple (0, 1, x)) + + fibResult :: (Integer, Integer, Integer) -> Integer + fibResult (x, y, z) = x + + fibTuple :: (Integer, Integer, Integer) -> (Integer, Integer, Integer) + fibTuple (x, y, 0) = (x, y, 0) + fibTuple (x, y, index) = fibTuple (y, x + y, index -1) \ No newline at end of file