Remember that Haskell adds implicit parenthesis for curried functions to the left and that spacing does not matter, only parenthesis do.
Warmup: Valid or Invalid
For each piece of code (expression or declaration), is it okay, or is it nonsensical in some way (if it doesn't make sense, what is wrong?)
1 + 1
1 +
Int + 1
test1 :: Int -> Int -> Bool
test2 :: Int -> 3 -> Bool
test3 :: True -> False -> True
f 0 = 0
f Int = 0
g [] = True
g h:t = False
Answers:
1 + 1
Okay
1 +
Nonsensical.
You need parentheses to make this a function.
(1 +) or (+ 1) would both be fine, and are syntactic sugar for (\x. 1 + x) and (\x. x + 1) respectively.
With parenthesis, this would be valid for any binary operator.
Int + 1
Nonsensical.
You can't add a type to a number (or any value).
test1 :: Int -> Int -> Bool
Okay
This is implicitly understood as Int -> (Int -> Bool). That is, test1 takes in an Int and returns a function that takes in an Int and returns a Bool.
Implicit () for types go to the right (but in curried functions they go to the left).
test2 :: Int -> 3 -> Bool
Nonsensical.
3 is not a type.
test3 :: True -> False -> True
Nonsensical.
True and False are Booleans, not types.
f 0 = 0
Okay.
We use things like this for pattern matching.
f Int = 0
Nonsensical.
We can't pattern match on types.
The only way to make this work would be to add a value called Int, which is possible to do in Haskell.
g [] = True
Okay.
This is also pattern matching.
g h:t = False
Nonsensical.
The parser tries to do (g h):t.
Remember that functions bind tightly.
Use parentheses with complicated arguments.
g (h:t) = False
Additional Examples:
('2', True)
['2', True]
Answers:
('2', True)
Okay.
Tuples can have elements with different types.
['2', True]
Nonsensical.
Lists must have elements that are all the same type.
Wiki Extra: Guards, if and case statements
In this bonus material, we'll play with the factorial function!
Some neat ways to write the factorial function include
fact n = product [1..n]
fact n = foldr (*) 1 [1..n]
fact n = foldr1 (*) [1..n]
You may want to look at the type of foldr and foldr1 to see how these work.
But normally we're write factorial recursively, like this:
fact 0 = 1
fact n = n * fact (n-1)
This uses pattern matching. It's basically syntactic sugar for a case statement like this
fact n = case n of 0 -> 1
_ -> n * fact (n-1)
Generally, we like pattern matching over if statements, but we could use an if statement like this:
fact n = if n < 1 then 1
else n * fact (n-1)
But a nicer notation than an if is often to use guard syntax:
fact n | n < 1 = 1
| otherwise = n * fact (n-1)
If you look on-line, people will often show guards with multiple clauses ending with otherwise (which is just the same as True), but you can just have one clause. If none of the guards are true, matching proceeds with the next pattern, so
we could write the base case last like this:
fact n | n >= 1 = n * fact (n-1)
fact _ = 1
Here's a better example of a guard without an otherwise:
commonPrefix (x:xs) (y:ys) | x == y = x :: commonPrefix xs ys
commonPrefix _ _ = []
if we wrote this code with an if, it'd look like this
commonPrefix (x:xs) (y:ys) = if x == y then x :: commonPrefix xs ys
else []
commonPrefix _ _ = []
Notice that in this version, we have to list the [] result twice, which is less cool.
Types & Typeclasses
What's the Type?
'c'
True
[True,False]
('c', True, GT)
"Hello"
['h','e','l','l','o']
\x -> x ++ " World"
\x -> x
\x -> \y -> (x,y)
\x -> \y -> [x,y]
\x -> \y -> x == y
\x -> "--> " ++ show x
\x -> x+1
42
Here's what you get when you enter each expression into ghci with :t
'c' :: Char
True :: Bool
[True, False] :: [Bool]
('c', True, GT) :: (Char, Bool, Ordering)
In other words: "Triple with Char, Bool, Ordering"
We haven't seen this in practice yet, but Ordering is a type with values LT, EQ, and GT.
"Hello" :: [Char]
Note that String is really just the same thing as [Char]. Haskell will default to the latter, unless you explicitly type something using String, as in let mystring :: String in ghci.
['h','e','l','l','o'] :: [Char]
This is equivalent to the previous, but without syntactic sugar.
\x -> x ++ " World" :: [Char] -> [Char]
Neat! Haskell automatically infers the types for the function's input and output.
\x -> x :: a -> a
This has an implicit "for all types a"
\x -> \y -> (x,y) :: a -> b -> (a, b)
\x -> \y -> [x,y] :: a -> a -> [a]
...and it infers the need for type variables.
Remember that lists are homogeneous.
\x -> \y -> x == y :: Eq a => a -> a -> Bool
...and for typeclasses.
Recall that Eq is a typeclass wherein all types define the == operator. Eq contains many types, but notably does not include functions (since function equivalence is undecidable).
Recall that == operates only on two arguments of the same type, so you can't for instance compare a Float with an Int. However, in the case of something like 3 == 3.2, Haskell simply infers that both arguments are Floats.
\x -> "--> " ++ show x :: Show a => a -> String
Recall that Show is a typeclass wherein all types define the show operator, which turns a value into a string.
\x -> x+1 :: Num a => a -> a
42 :: Num a => a
Class Notes:
Values, Types, and Typeclasses
In Haskell, Values are instances of Types. This relationship is similar to set membership where the values are members of types.
Example values include:
'c'
True
GT (and LT and EQ)
[True, False]
"Hello"
42
The Types of these values, respectively, are:
Char
Bool
Ordering
[Bool]
[Char]
At this point, we cannot know, as there are many types that encode numerical values. You can give this value an explicit type by writing 42 :: Integer instead.
Above Types, there are Typeclasses. These are detailed in the next section, and help by grouping types by which operations can be performed on them. For instance, by writing the function \x -> \y -> x < y, Haskell knows that x and y must be values of the same type, which is part of the Ord Typeclass (which contains all types which have a defined method of comparison).
Haskell's families of types — Typeclasses
Recall that values have types. Types belong to type classes.
Eq defines the == and /= operators
Ord defines comparison operators, such as <
Note that any type in Ord must also be in Eq
Bounded defines minBound and maxBound, so for instance, minBound :: Int gives -2147483648
Enum defines the succ and pred operators, and also allows a type to be used in list ranges
Read defines the read operator which turns a String into a value
Note that Haskell needs to be able to infer what the resulting type should be, so something like read "4" + 2 or (read "4") :: Int will work, but simply read "4" will not.
Show defines the show operator which turns a value into a String.
Num defines +, -, /, etc...
Haskell's data Declarations
Haskell is not an object oriented language, so there are no classes, but rather types and type classes.
Some types are predefined like Bool and Ordering, but there's nothing really special about these types. Here are their type signatures:
Here Bool and Ordering are types and False, True, LT, EQ, and GT are values.
Note: Ordering is a type while Ord is a typeclass. They are not the same thing!
Defining Our Own Types
We are able to define our own types by using data declarations.
These newly defined types will not belong to any typeclasses, such as Eq or Show, even though you will be able to use them as literal constants in expressions and pattern matching. You might also think you can “see them” when you use :t on an expression (because it just prints what you typed and so it looks like it's printing out values of the type even though it isn't). To get Haskell to include the corresponding "obvious" definitions of == and show, you use deriving, as shown below.
If you wanted make sure Bool could be used where things of the Show type class are used, we could define show for the type Bool.
instance Show Bool where
show :: Bool -> String
show True = "True"
show False = "False"
Similarly, here is how you would define the == operator on Bools.
Sometimes, you can have Haskell logically write functions that allow a type to be in an existing type class by using deriving (...). Here are some examples of type declarations:
You can also give different names to types that already exist. For example, you can say that Strings are the same as [Char]. Then, if you input :info String into ghci, you will get type String = [Char]. However, ghci will still identify "Hello" and other strings as [Char].
When using this method to derive Ord, the ordering is determined by the order in which the possible values are written. For example, when we write a new type as such:
dataMystery = A | Bderiving (Eq, Ord)
The comparison A < B will return True.
Using a type
dataThermostatSetting = Off
| Cooling
| Heatingderiving (Show, Eq, Ord)
Let's define isRunning :: Int -> ThermostatSetting -> Bool
We can use pattern matching! Note that when you're using associated values in pattern matching, you need to put parentheses around the whole value, as in (CoolTo t) (otherwise Haskell would think you were defining a function with three arguments).
isRunning _ Off = False
isRunning _ (OutOfService _) = False
isRunning temp (CoolTo t) = temp > t
isRunning temp (HeatTo t) = temp < t
dataCrayon = Red | Orange | Yellow | Green
| Blue | Purple | Black | White
| RGBIntIntIntderiving (Show, Ord, Eq)
dataComplex = RectFloatFloat
| PolarFloatFloatderiving (Show)
Note that in the case of Complex, we don't want to use the default implementations of == or <, because we need to do some actual math to compare rectangular and polar coordinates. Thus we don't use deriving for Eq or Ord.
Associated values should look totally new to you, unless you've worked with something like Swift (enums with associated values), Pascal (variant records/variants), Rust (discriminated/tagged unions), or ML.
C and C++ have a construct that is similar in that it can store one of several data types (union), however, it lacks the "tag" that lets us know which type we have. Associated values can be used by either creating a custom datatype or using the visitor design pattern.