-------------------[ Readings ]------------------- We'll use "Real World Haskell", by Bryan O'Sullivan, Don Stewart, and John Goerzen, http://book.realworldhaskell.org/read/ . Read Chapters 1--3. There is also "Learn You a Haskell for Great Good!" by Miran Lipovaca, http://learnyouahaskell.com/chapters . You may find its style more entertaining. ===================[ Getting used to Haskell's syntax ]=================== Haskell is the language where leading edge ideas are implemented and tested (as Scheme has been at the time when lexical bindings, first-class closures, and continuations were the leading edge). Unlike Scheme's and LISP's minimalistic approach to syntax, Haskell has a lot of syntax complexity. I feel that some of that complexity gets in the way of understanding which constructs are core and which are "syntactic sugar" and what they expand to. So it's important to spend some time with the GHCI shell (or Hugs) getting used to it. Be warned that the older Hugs and the newer and actively developed GHC support different syntaxes, and what works in one may not work in the other if deprecated or added in the meantime. Your initial experience with Haskell will be full of syntax errors. In Haskell, both function calls and creating lambdas by specifying some (but not all) arguments of a function need no special syntax: two tokens separated by a space may _already_ mean a function application, full or partial! So you may mean to do one thing, like write a pattern, but your actual expression does another, such as applies a function instead. In that case, you will likely get a confusing type error. You'll get the hang of it, and you'll get to like this syntax, but it needs practice. Here are a couple of links on understanding Haskell error messages: http://stackoverflow.com/questions/10375532/haskell-understanding-no-instance-for-error-messages-in-ghci http://ics.p.lodz.pl/~stolarek/_media/pl:research:stolarek_understanding_basic_haskell_error_messages.pdf Here are some notes on Haskell's syntax and the concepts behind it. ------------------------------------------------------------------------ 1. Lists: constructors and pattern matching are core; the rest is sugar. ------------------------------------------------------------------------ Read through Chapters 1--3 of Real World Haskell (http://book.realworldhaskell.org/read/) to get the hang of the basic list syntax. The most important thing to understand is that lists can only be constructed with the constructors : and [], and can only be operated on via pattern-matching these constructors. All other ways of describing lists (like [1,2,3,4] or "abcdefgh") reduce to these constructors (see https://www.haskell.org/onlinereport/exps.html#lists) and all functions that operate on lists must start with patter-matching on : and [] (read, e.g., the list functions in Prelude: look for the sources of tail, head, last, null, etc. in the https://www.haskell.org/onlinereport/standard-prelude.html, then look at ++ , map, and filter.) Just like LISP (and unlike Ruby or Java), lists are entirely transparent; unlike LISP, where writing a function to operate on a list you need to write the calls to the CAR and the CDR, in Haskell you only need to write the pattern to bind some variables to the head and the tail of a cons---and this "deconstruction" by pattern is really the only way to get them (of course, you can call functions like head and tail, but they just wrap these patterns, trivially). This is true for _all_ data types in Haskell: you can only build them with constructors, and you can only pick them apart for processing with pattern matching. There are no opaque special ways or structure hidden in "objects" that only "methods" can manage. The constructors : and [] are internal, but we can define our own list type that is isomorphic to the built-in. See List example below. --------------- 2. Parentheses. --------------- In C/C++, Java, and many other languages () is an operator that calls a function (and gets compiled down to "call" or "call*" instruction, ultimately). In LISP, parens are the main and only element of syntax. In Haskell, they are nothing of either kind, and are used only for affecting the order of operations or to group elements of a pattern---or not at all, when that order works out without them, or when $ is used: f $ g $ h x is f (g (h x)) (see https://hackage.haskell.org/package/base-4.8.0.0/docs/Data-Function.html) Note that without the parens f g h x is ((f g) h) x ! That's function f getting g as an argument and producing another function that gets h as an argument, and makes another function that finally is called on x. Intuitive this is not, and getting used to reading it requires time, but that time is a good investment. Function application ("calling a function") only needs the space between the function name and the argument. Work out what value answer computes, then run it, then understand why: answer = twice twice twice suc 0 twice f x = f (f x) suc x = x + 1 Note that the ()s above are the only ones actually needed. Hint: work out what a single application of twice does to a function. [[This example is from "An Overview of Miranda" by David Turner, https://www.cs.kent.ac.uk/people/staff/dat/miranda/overview.pdf; Miranda, a non-free language, was supplanted by Haskell, which built on it. Read the above paper for its history value, though, and for succinct explanations of the concepts that became the core of Haskell. Miranda has some patterns and operators that later Haskell abandoned. See prev.hs and note that '--' is Haskell's comment, not an operator as it is in Miranda! Suggestion: rewrite this code in Haskell so that it works.]] Since Haskell's function application is the most basic syntactic thing (it's hard to be more basic that a space between two tokens!), it's easy to mistakenly write a function application when you don't mean it. The resulting expression will likely not typecheck. For example, in the following parens are needed: head (x:xs) = x Without them, 'head x : xs' would look like an application of head to x, the result for some reason being :-ed with xs. This is not the list-matching pattern we were looking for! ---------------------------- 3. Prefix vs infix functions ---------------------------- Languages like C/C++ and Java distinguish between operators and functions (even though C++ allows one to define the meaning of an operator like '<<' with a function 'operator<<(..)'). In Haskell, you only have functions, but some of these functions support "infix" syntax. For example, + as in 2+3 is a function referred to as (+), and can be used as such: firefly:hs user$ ghci GHCi, version 7.8.3: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Prelude> (+) 2 3 5 Prelude> :i (+) class Num a where (+) :: a -> a -> a ... -- Defined in ‘GHC.Num’ infixl 6 + The last line specifies + as an infix operator, associating to the left, with precedence 6. The left association means that 1+2+3+4 is actually ((1+2)+3)+4 By contrast, right association of (:) means that 1:2:3:4:[] is 1:(2:(3:(4:[]))), which, of course, is the only way a list can be constructed (see #1 above). Prelude> :i (:) data [] a = ... | a : [a] -- Defined in ‘GHC.Types’ infixr 5 : in Hugs, we see the same, but with slightly different syntax: Hugs> :i ++ infixr 5 ++ (++) :: [a] -> [a] -> [a] Hugs> :i : infixr 5 : (:) :: a -> [a] -> [a] -- data constructor Now notice that 1+2:3:[] means [3,3] not 1+(2:3:[]), which would produce a type error from trying to add a number to a list. This is because + binds stronger than : . The former has priority 6, the latter priority 5, and so + wins over : . For the relative precedence table of various common operators, see https://www.haskell.org/onlinereport/decls.html#fixity These priorities and associations (a.k.a. "fixity") define the form of the parsed Haskell sexp---i.e., which operation's node gets to be the parent and which one its child. Observe that 1:[] ++ 2:[] makes [1,2]. You'd think the order of operations would be (1:[]) ++ (2:[]) , but in fact the precedence of both : and ++ is 5, and both are right-associate, so it's actually 1 : ([] ++ (2:[])). Which works out to the same value [1,2]---check this! [I verified this with the ghc-dump-tree package that exposes the parsed Haskell sexps after syntactic desugaring and type-checking transformations. Look for the final ##Typechecked tree and be prepared to write tree simplifier for this sexp, as it includes many kinds of information besides the parse.] -------------------------------------------- 4. Functions of several arguments. Currying. -------------------------------------------- In Haskell, every function of two or more arguments is a higher-order function (this is almost a verbatim quote from the Miranda overview above). You can see this from the type notation that Haskell provides for its functions. For example, (+) is conventionally a function of two arguments. For example, 2+3 is (+) 2 3 is 5. But observe the type signature of (+): Prelude> :t (+) (+) :: Num a => a -> a -> a The "Num a =>" part is understandable: in order to be added, the values must be some kind of a numeric type, so the type variable "a" gets this additional constraint on it. But which part says "two arguments"? Compare this with a function that takes a number and returns a function that applies to a number and gets you a number: :t \ x -> ( \ y -> x + y ) \ x -> ( \ y -> x + y ) :: Num a => a -> a -> a There's no apparent difference in the type. Moreover, there's no difference in their operation: Prelude> (+) 2 3 5 is the same as: Prelude> ((+) 2) 3 5 Prelude> :t (+) 2 ((+) 2) :: Num a => a -> a Prelude> (\ x -> ( \ y -> x + y ) 2) 3 5 is the same as: Prelude> ( \ x -> ( \ y -> x + y )) 2 3 5 Prelude> :t \ x -> ( \ y -> x + y ) 2 (\ x -> ( \ y -> x + y ) 2) :: Num a => a -> a Indeed, the classic LISP lambda in which one argument of the two is fixed, (funcall ((lambda (x) (lambda (y) (+ x y))) 2) 3) => 5 would have the same type if LISP were typed. In LISP or Scheme, creating a function with a smaller number of arguments by closing over some arguments of a function with more arguments requires writing code as above, with explicit lambdas (or defuns). In contrast, for Haskell multi-argument functions, simply supplying some (but not all) arguments automatically creates a lambda in the remaining arguments! Just like with function calls via a mere space, the syntax of Haskell makes creating these lambdas very easy; sometimes, you may do so without realizing it. Moreover, these is no discernible difference between a function f that takes a parameter of type a and returns a function that maps another parameter of type b to whatever return type c, f :: a -> (b -> c) and a function f' that takes *two* arguments of types a and b and makes a c . In Haskell's notation, *both* are shown the same way: f' :: a -> b -> c Note that taking a function from type a to type b as an argument (i.e., taking an argument of the type a->b) is depicted differently: g :: (a->b) -> c For example: Prelude> :t map map :: (a -> b) -> [a] -> [b] Prelude> map show [1,2,3,4] ["1","2","3","4"] Prelude> map ( \ x -> x*x ) [1,2,3,4,5] [1,4,9,16,25] Prelude> map ( \ x -> (x, "foo") ) [1,2,3,4,5] [(1,"foo"),(2,"foo"),(3,"foo"),(4,"foo"),(5,"foo")] --varying different expressions that result in equivalent -- evaluations: Prelude> map ( \ x -> "foo" ++ show x ) [1,2,3,4,5] ["foo1","foo2","foo3","foo4","foo5"] Prelude> map ( \ x -> ((++) "foo" . show) x) [1,2,3,4,5] ["foo1","foo2","foo3","foo4","foo5"] Prelude> map ( \ x -> ((++) "foo") . show $ x) [1,2,3,4,5] ["foo1","foo2","foo3","foo4","foo5"] Prelude> map (\ x -> (++) "foo" (show x)) [1,2,3,4,5] ["foo1","foo2","foo3","foo4","foo5"] ------[ Typos that accidentally became patterns ]------- The so-called n+k pattern is no longer a part of GHC Haskell, but is still present in Hugs. At one point in class, I was meaning to type a list comprehension [ x+2 | x <- [1,2,3,4] ] => [3,4,5,6] (see https://www.haskell.org/onlinereport/exps.html#list-comprehensions for list-comprehensions) Instead, I typed: [ x | x+2 <- [1,2,3,4] ] , and, instead of a type mismatch error or some such, got [0,1,2] in Hugs (in GHC, this expression is no longer syntactically valid, and fails to parse). This is the list of positive integers that make the elements of the [1,2,3,4] list if added to 2. This pattern is probably inspired by the rules like fact (n+1) = (n+1) * fact n ---but this pattern from Miranda turned out to be more confusing than useful. Instead, to make the return of a function or an operation into a valid pattern Haskell opted for the so-called _view patterns_, https://ghc.haskell.org/trac/ghc/wiki/ViewPatterns . The best way of handling such patterns is still a PL research question! ------[ A lazy infinite list of Fibonacci numbers ]----- In Haskell, evaluation is lazy. *All* expressions are kept as read or entered, until they must be evaluated or pattern-matched. This gives rise to the ability to construct *infinite( lists via recursive relations---and the elements of these lists are only evaluated when required. At all other times, these expressions as stored as parsed sexps or, for infinite sequences, as their generating functions, called only when the valued of the expression so represented is asked for. Thus, for Fibonacci numbers fib = 1 : 1 : [ a+b | (a,b) <- zip fib (tail fib) ] take 100 fib gives 100 Fibonacci numbers! Using the so-called as-pattern, this matching can be made more efficient: fib@(1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ] [see about @ : https://www.haskell.org/tutorial/patterns.html] take 100 fib still works Start with looking up 'zip. It's fairly simple function that takes two lists and returns a list of 2-tuples where there the first element is drawn from the first list, and the second from the second. The elements of a longer lists---if the lists are of different lengths---are ignored. The figure out the above two forms of plain recursion and pattern matching. We will look at such examples further. -----------------[ Making a simple new type ]----------------- All data types in Haskell are built as the basic list: with some explicit constructors, which are then used in pattern matches whenever you want to actually do something with the type. To put it differently, to do anything with an object, you need to "deconstruct" it first, matching the constructors from which it has been constructed. These constructors are, in a sense, just special tags; you can think of each as a special kind of a cons that makes a sexp tagged with the constructor's symbol. In LISP and Scheme, you can mix-and-match cons cells as you like, and join them into any structures; in Haskell and Miranda, you do get the same construction capabilities, but you cannot mix-and-match different types. For example, (list 1 5) is a perfectly good representation of the coordinates of a point in some 2-dim grid---but you can also append another list to it. In Haskell, you would write something like data Point a = Pt a a and that gives you the ability to construct pairs of coordinates (essentially, lists) called "Points" with the special "cons" called Pt. When so constructed, such conses cannot be mixed up with anything else and must be pattern-matched to do anything with their coordinates. Point is called the type constructor, Pt a _value_ constructor. The same token can be used for both, and always is. Whenever you need to make a Point from some two numbers or pattern-match these numbers of out a Point, you use Pt. Actually, even printing them will need some action on your part: *Main> data Point a = Pt a a *Main> Pt 1 5 :13:1: No instance for (Show (Point a0)) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it A quick way of getting around this is to have Haskell derive the show() function for Point automatically when you define Point. After all, how hard it is to print a glorified cons? LISP's REPL printed absolutely any structures you created---as lists :) *Main> data Point a = Pt a a deriving (Show) *Main> Pt 1 5 Pt 1 5 We can define our own show() function for Point (with some false starts): *Main> instance Show (Point a) where show Pt x y = "<" ++ show x ++ " " ++ show y ++ ">" :22:36: Constructor ‘Pt’ should have 2 arguments, but has been given none In the pattern: Pt In an equation for ‘show’: show Pt x y = "<" ++ show x ++ " " ++ show y ++ ">" In the instance declaration for ‘Show (Point a)’ Argh, I forgot the parens in the pattern! Trying again: *Main> instance Show (Point a) where show (Pt x y) = "<" ++ show x ++ " " ++ show y ++ ">" :23:54: No instance for (Show a) arising from a use of ‘show’ Possible fix: add (Show a) to the context of the instance declaration In the first argument of ‘(++)’, namely ‘show x’ In the second argument of ‘(++)’, namely ‘show x ++ " " ++ show y ++ ">"’ In the expression: "<" ++ show x ++ " " ++ show y ++ ">" Another error! I am using "show x" in the expression, but who says there is a "show" for the types of x? Well, I need to promise it: *Main> instance (Show a) => Show (Point a) where show (Pt x y) = "<" ++ show x ++ " " ++ show y ++ ">" Did it work? Seems to have worked! Or did it? Let's try: *Main> Pt 2 7 :25:1: Overlapping instances for Show (Point a0) arising from a use of ‘print’ Matching instances: instance Show a => Show (Point a) -- Defined at :14:33 instance Show a => Show (Point a) -- Defined at :24:10 In a stmt of an interactive GHCi command: print it Ah, but we already have one show for Point, the Haskell's default one, created by "deriving". They collide. I need to get rid of that. Going back to the definition of Point without "deriving": *Main> data Point a = Pt a a *Main> instance (Show a) => Show (Point a) where show (Pt x y) = "<" ++ show x ++ " " ++ show y ++ ">" *Main> Pt 2 7 <2 7> Let's say we want to "add points" as vectors. I want to write this function at the prompt. Alas, writing a function definition at the Hugs prompt will fail. Hugs needs it to be in a file, loaded with :l), but luckily GHCI lets us to do it with a special syntax of "let". More about working in the GHCI shell: https://downloads.haskell.org/~ghc/7.8.4/docs/html/users_guide/interactive-evaluation.html *Main> addpt (Pt x y) (Pt z t) = Pt (x+z) (y+t) :29:25: parse error on input ‘=’ "let" to the rescue: *Main> let addpt (Pt x y) (Pt z t) = Pt (x+z) (y+t) *Main> addpt (Pt 1 5) (Pt 2 7) <3 12> Observe the different types of things we just created: *Main> :t addpt addpt :: Num a => Point a -> Point a -> Point a A lambda/partial application will be automatically created, too: *Main> :t addpt (Pt 1 5) addpt (Pt 1 5) :: Num a => Point a -> Point a Constructor: *Main> :t Pt Pt :: a -> a -> Point a Partial application thereof: *Main> :t Pt 1 Pt 1 :: Num a => a -> Point a Constructed object: *Main> :t Pt 1 2 Pt 1 2 :: Num a => Point a What about "Point"? Well, it's not a data constructor, it's a type. We chose to name the data constructor 'Pt'. We can't make values with Point: *Main> :t Point :1:1: Not in scope: data constructor ‘Point’ But, in Haskell terms, Point has a "kind". It takes one type ("a") and makes a new type ("Point") out of it. *Main> :k Point Point :: * -> * If Point took two types, it's "kind" would've been * -> * -> * and so on. Notice at which points the context constraint "Num a =>" appears! Rewinding a bit: *Main> data Point a = Pt a a *Main> :t Pt Pt :: a -> a -> Point a *Main> let addpt (Pt x y) (Pt z t) = Pt (x+z) (y+t) *Main> :t Pt Pt :: a -> a -> Point a *Main> :t Pt 1 2 Pt 1 2 :: Num a => Point a *Main> :t Pt Pt :: a -> a -> Point a *Main> addpt (Pt 1 5) (Pt 2 7) :47:1: No instance for (Show (Point a0)) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it Yeah, we forgot the show, but the value still gets created, it's only show that gives us the problem. We'll fix it later. *Main> :t Pt Pt :: a -> a -> Point a *Main> :t addpt addpt :: Num a => Point a -> Point a -> Point a *Main> :t Pt "foo" "bar" Pt "foo" "bar" :: Point [Char] *Main> addpt (Pt "foo" "bar") (Pt "baz" "quux") :51:1: No instance for (Num [Char]) arising from a use of ‘addpt’ In the expression: addpt (Pt "foo" "bar") (Pt "baz" "quux") In an equation for ‘it’: it = addpt (Pt "foo" "bar") (Pt "baz" "quux") *Main> let addpt (Pt x y) (Pt z t) = Pt (x++z) (y++t) *Main> :t addpt addpt :: Point [a] -> Point [a] -> Point [a] *Main> addpt (Pt "foo" "bar") (Pt "baz" "quux") :54:1: No instance for (Show (Point [Char])) arising from a use of ‘print’ In a stmt of an interactive GHCi command: print it *Main> instance (Show a) => Show (Point a) where show (Pt x y) = "<" ++ show x ++ " " ++ show y ++ ">" *Main> addpt (Pt "foo" "bar") (Pt "baz" "quux") <"foobaz" "barquux"> *Main> *Main> addpt (Pt 1 5) (Pt 2 7) :57:1: No instance for (Num [a0]) arising from a use of ‘it’ In a stmt of an interactive GHCi command: print it What happened? We replaced the definition of addpt to handle strings (well, lists of anything, really: [a]) and now it no longer knows what to do with integers. Redefine it: *Main> let addpt (Pt x y) (Pt z t) = Pt (x+z) (y+t) *Main> addpt (Pt 1 5) (Pt 2 7) <3 12> (Note that now we can custom-print the point.) But now we lost the string one: *Main> addpt (Pt "foo" "bar") (Pt "baz" "quux") :61:1: No instance for (Num [Char]) arising from a use of ‘addpt’ In the expression: addpt (Pt "foo" "bar") (Pt "baz" "quux") In an equation for ‘it’: it = addpt (Pt "foo" "bar") (Pt "baz" "quux") In LISP or Scheme, we could write a function that did different things to different types, based on some conditions---e.g., one thing for numeric atoms, another for strings, and maybe yet another for other lists. In Haskell, we can also do that, but we'll need to define _the actual type_ on which this function would operate, or else nothing would typecheck! Indeed, Haskell offers ways to define types that may take values in several different other types (a _sum_ of types), but we need to do this explicitly, with separate data constructors, and must then pattern-match the types out before operating on them. We'll do that next class. -------------[ A new List isomorphic to builtin list ]------------- Point was a simple type like a two-cell cons list with a tag, or a dotted pair in LISP. In C, it would be a struct, perhaps with a typedef; in C++ or Java or Ruby, a class with some setter and getter methods. Now we want to construct a _recursive_ type that behaves exactly like the built-in list, constructed by consecutive applications of : and some elements of a type a to some initial application of []. Recall that [1,2,3] is actually and exactly 1 : (2 : (3 : []), or, since : is "infixr" meaning right-associative, 1 : 2 : 3 : [] , and the same for any other lists. So here is this list type. Compare its functions with those of the Prelude's built-in list type: -------------- mylist.hs -------------- data List a = Cons a (List a) | Nil --We could have it "deriving( Show )", but I want my --own printable/string representation, so: instance Show a => Show (List a) where show (Cons a b) = "<" ++ show a ++ " # " ++ show b ++ ">" show Nil = "<>" -- head, tail, length, null hd :: List a -> a hd (Cons x _) = x -- What to do for hd Nil? tl (Cons _ xs) = xs tl (Cons _ Nil) = Nil -- What to do for tl Nil? ln (Cons x xs) = 1 + ln xs ln Nil = 0 nl Nil = True nl (Cons _ _) = False (#) a b = Cons a b --------------------------------------- *Main> :l mylist.hs [1 of 1] Compiling Main ( mylist.hs, interpreted ) mylist.hs:15:1: Warning: Pattern match(es) are overlapped In an equation for ‘tl’: tl (Cons _ Nil) = ... Ok, modules loaded: Main. *Main> 1 # Nil <1 # <>> it :: Num a => List a *Main> 1 # (2 # Nil) <1 # <2 # <>>> Trying it without parens: *Main> 1 # 2 # Nil :98:1: No instance for (Num (List a0)) arising from a use of ‘it’ In a stmt of an interactive GHCi command: print it That's because (#) associates to the left, be default. We thus get (1 # 2) # Nil, and there's no matching pattern for (1 # 2). Adding "infixr 5 #" just before the definition of # and reloading: *Main> 1 # 2 # Nil <1 # <2 # <>>> *Main> hd 1 # Nil :110:1: No instance for (Num (List a0)) arising from a use of ‘it’ In a stmt of an interactive GHCi command: print it Argh, forgot the parens again! Without them, I get hd applied to 1, which isn't defined. Correcting: *Main> hd $ 1 # Nil 1 *Main> tl $ 1 # 2 # Nil <2 # <>> *Main> ln $ 1 # 2 # Nil 2 *Main> nl Nil True --- Types: *Main> :t hd hd :: List a -> a *Main> :t tl tl :: List t -> List t *Main> :t ln ln :: Num a => List t -> a *Main> :t nl nl :: List t -> Bool *Main> :t Cons Cons :: a -> List a -> List a *Main> :t Nil Nil :: List a Finally, errors. This is what falling off the pattern list for a function looks like: *Main> tl Nil *** Exception: mylist.hs:(16,1)-(17,21): Non-exhaustive patterns in function tl After adding a special declaration to hd: hd Nil = error "no head in an empty List" we get instead: *Main> hd Nil *** Exception: no head in an empty List I had some errors in class trying to define a custom error handler for my List class; we'll talk about this in the next lecture. --------------[ Lazy evaluation, errors included ]-------------- In Haskell, expressions are not evaluated until their values are needed. Haskell will store expressions (in some optimized form), and only evaluate them when needed. This includes errors. Consider the following partial application of (+): Prelude> let f = (+) 2 Prelude> f 4 6 As expected. Now what if we want the argument to blow up the function call? Easy: Prelude> let f = (+) (error "Kaboom!") Prelude> :t f f :: Num a => a -> a So it's a legit function. You can use it in other expressions: Prelude> let g = \x -> 3 + f x Prelude> :t g g :: Num a => a -> a But if we call it: Prelude> f 4 *** Exception: Kaboom! Prelude> g 5 *** Exception: Kaboom! Note that if the expression doesn't get to actually calling f, no kaboom: Prelude> let g = \x -> if x > 5 then x else 3 + f x Prelude> g 10 10 Prelude> g 6 6 Prelude> g 5 *** Exception: Kaboom! Note that g has a new typeclass constraint, Ord a, inferred from the fact that we use the argument x in a comparison > . We did not see it before, just (Num a), inferred from +, was sufficient. Prelude> :t g g :: (Ord a, Num a) => a -> a You may ask: why do such expressions typecheck at all? After all, if you cannot add strings, how come you can "add" an error to a number, like f, judging by its type signature, seems to offer to do? The informal answer is that "error" has a special type that is a member of any other type: Prelude> :t error error :: [Char] -> a Practically, this depends on the compiler treating it as a special case. Looking where error is defined, Prelude> :i error error :: [Char] -> a -- Defined in ‘GHC.Err’ https://hackage.haskell.org/package/base-4.8.2.0/docs/src/GHC.Err.html you'll see some comments to this effect.