Chapter 7: More Functional Patterns¶
I think I’ll respond to the Sussman quote with an epigraph from chapter 1 of SICP.
The acts of the mind, wherein it exerts its power over simple ideas, are chiefly these three:
Combining several simple ideas into one compound one, and thus all complex ideas are made.
The second is bringing two ideas, whether simple or complex, together, and setting them by one another so as to take a view of them at once, without uniting them into one, by which it gets all its ideas of relations.
The third is separating them from all other ideas that accompany them in their real existence: this is called abstraction, and thus all its general ideas are made.
—John Locke, An Essay Concerning Human Understanding (1690)
7.1 Make it func-y¶
This chapter explores common idioms enabled by first-class functions, pattern matching, and composition. Particularly, we will:
learn about the mechanics of pattern matching;
explore case expressions and guards – elegant control flow constructs that allow selection based on pattern matches and boolean expressions;
nest functions, experiment with lambda expression;
use combinators that change the order of function application, like
(.)
and$
.
7.2 Arguments and parameters¶
Functions are defined by the fact that they can be applied to an argument and return a result.
A parameter is a name that acts as a placeholder for an input within a function definition. An argument is an input value that is applied to the function.
When you apply a function to an argument, the value of that argument is bound to a parameter name within the definition for that particular instance of the function, or that function call.
·∾ -- v parameter
·∾ id x = x
·∾ -- v argument
·∾ id 3 -- parameter x is bound to 3 for this particular function invocation.
3
Functions are applied to arguments which binds their parameters to values.
7.2.1 Setting parameters¶
As you add parameters infix type constructors for curried functions and their respective input type variables are added to the type signature.
·∾ -- Notice what happens to the type
·∾ -- signature as we introduce more
·∾ -- parameters.
·∾ myNum = 1 :: Integer
·∾ myVal f g = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> Integer
·∾ myVal f g h = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> Integer
·∾ myVal f g h i = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> p4 -> Integer
Saturating a parameter with an argument removes one -> a
from the
type signature.
·∾ -- And what happens as we apply
·∾ -- arguments to those parameters.
·∾ myVal f g h i = myNum
·∾ :type myVal
myVal :: p1 -> p2 -> p3 -> p4 -> Integer
·∾ :type myVal 1
myVal :: p1 -> p2 -> p3 -> Integer
·∾ :type myVal 1 2
myVal :: p1 -> p2 -> Integer
·∾ :type myVal 1 2 3
myVal :: p1 -> Integer
·∾ :type myVal 1 2 3 4
myVal :: Integer
7.2.2 Binding variables to values¶
Haskell is lexically scoped. This means that resolving the value for a named entity depends on the names location in source code. The lexically innermost binding for a variable of a particular name always takes precedence. If an inner scope defines a name that already exists in the outer scope, it shadows that name, so the outer definition is no longer visible within that inner scope.
In the example here, the parameter x is shadowed by the definition for x within the let expression, essentially blocking the visibility of the parameter:
·∾ :{
⋮ bindExp :: Integer -> String
⋮ bindExp x = let x = 10; y = 5 in
⋮ "x: " ++ show x ++
⋮ ", " ++
⋮ "y: " ++ show y
⋮ :}
·∾ bindExp 8
"x: 10, y: 5"
In a repl session, each line shadows the next. The mechanics of this are conceptually similar to nested lambda expressions.
·∾ x = 5
·∾ y = x + 5
·∾ y
10
This analogy begins to break down when you consider top-level declarations, like imports, though.
Also note that multi-line expressions are treated as one logical “line”.
·∾ -- multi-line expressions are don't shadow names until they're ended
·∾ :{
⋮ x = 3
⋮ x = 4
⋮ :}
<interactive>:93:1: error:
Multiple declarations of ‘x’
Declared at: <interactive>:91:1
<interactive>:93:1
7.3 Anonymous functions¶
Anonymous functions are function literals, or functions without a name.
Here is a named function:
triple :: Integer -> Integer
triple x = x * 3
…and here is the same function but with anonymous function syntax:
(\x -> x * 3) :: Integer -> Integer
7.3.1 Grab Bag¶
Which (two or more) of the following are equivalent?
mTh x y z = x * y * z
mTh x y = \z -> x * y * z
mTh x = \y -> \z -> x * y * z
mTh = \x -> \y -> \z -> x * y * z
All of these are equivalent. Here’s proof:
·∾ mTh x y z = x*y*z ·∾ :type mTh mTh :: Num a => a -> a -> a -> a ·∾ mTh x y = \z -> x*y*z ·∾ :type mTh mTh :: Num a => a -> a -> a -> a ·∾ mTh x = \y -> \z -> x*y*z ·∾ :type mTh mTh :: Num a => a -> a -> a -> a ·∾ mTh = \x -> \y -> \z -> x*y*z ·∾ :type mTh mTh :: Num a => a -> a -> a -> a
The type of
mTh
(above) isNum a => a -> a -> a -> a
. Which is the type ofmTh 3
?Integer -> Integer -> Integer
Num a => a -> a -> a -> a
Num a => a -> a
Num a => a -> a -> a
·∾ :type mTh 3 mTh 3 :: Num a => a -> a -> a
Next we’ll practice writing anonymous lambda syntax.
Rewrite the
f
function in thewhere
clause.The named function:
addOneIfOdd n = case odd n of True -> f n False -> n where f n = n + 1
Becomes:
addOneIfOdd n = case odd n of True -> (\n -> n + 1) n False -> n
Proof it works:
·∾ addOneIfOdd 8 8 ·∾ addOneIfOdd 9 10
Rewrite the following to use anonymous lambda syntax:
addFive x y = (if x > y then y else x) + 5
Rewritten:
(\x y -> (if x > y then y else x) + 5)
Proof it works:
·∾ addFive x y = (if x > y then y else x) + 5 ·∾ addFive 4 10 9 ·∾ addFive 20 10 15 ·∾ (\x y -> (if x > y then y else x) + 5) 4 10 9 ·∾ (\x y -> (if x > y then y else x) + 5) 20 10 15
Rewrite the following so that it doesn’t use anonymous lambda syntax:
mflip f = \x -> y -> f y x
Rewritten:
mflip f x y = f y x
Proof it works:
·∾ mflip f = \x -> \y -> f y x ·∾ mflip (^) 2 5 25 ·∾ (^) 2 5 32 ·∾ mflip f x y = f y x ·∾ mflip (^) 2 5 25 ·∾ (^) 2 5 32
7.3.2 The utility of lambda syntax¶
You’ll most often use lambdas when you’re passing a function as an argument, and that’s the only place in your program where you’ll need it. If you aren’t going to call the function again, why give it a name?
7.4 Patten matching¶
Pattern matching allows you to do three things:
compare data constructors against an input (matching);
access arguments or fields of a data constructor (destructuring);
and bind names to successful matches of data constructors or their destructured arguments (name binding).
You can think of patterns as coming in two varieties; those composed only of values, and those that introduce names.
Value only patterns¶
Any data constructor can be matched, like True
, or 1
, or even compound
structures like (1,2)
, Just 3
, and [('c',2),('d',4)]
.
This function matches a pattern of only values, but doesn’t do any destructuring
or name binding. Even literal values are patterns. It returns True
if given
1
, and throws an exception for anything else:
isOne 1 = True
In ghci:
·∾ isOne 1
False
·∾ isOne 8
*** Exception: <interactive>:185:1-14: Non-exhaustive patterns in function isOne
Destructuring value only patterns¶
Here is a function that works on a more complicated structure. This pattern uses
destructuring to access sub-elements, but does not bind names. It will return
False
if you give it this very particular tuple literal:
contrived :: ([a], Char, (Int, Float), String, Bool) -> Bool
contrived ([], 'b', (1, 2.0), "hi", True) = False
In ghci:
·∾ contrived ([], 'b', (1, 2.0), "hi", True)
False
If you give it a different tuple, you’ll get an error, since we haven’t defined a catch-all pattern:
·∾ contrived ([], 'F', (8, 9.8), "bye", False)
*** Exception: <interactive>:165:1-49: Non-exhaustive patterns in function contrived
Patterns that introduce names¶
Patterns look a lot like data constructors, but in place of data constructor parameters you can supply a name, instead. If a value (or argument) inhabits that parameter, it gets bound to the name you’ve supplied.
Name binding and destructuring¶
Here is an example pattern that assigns the third element of a triple to a
and discards the rest:
·∾ (_,_,a) = (1,2,3)
·∾ a
3
The _
you see here is a wildcard – a special name that always matches and
throws away the result.
Mechanics of pattern matching¶
The matching process itself occurs “top-down, left-to-right.”
Pattern matching can either fail, succeed or diverge. A successful match binds the formal parameters in the pattern. Divergence occurs when a value needed by the pattern contains an error (⊥).
Failure of a pattern anywhere in one equation results in failure of the whole equation, and the next equation is then tried. If all equations fail, the value of the function application is ⊥, and results in a run-time error.
Patterns in any one equation are not allowed to have more than one occurrence of
the same formal parameter (a property called linearity). Except for the
wildcard, _
, which you can use more than once.
Lazy patterns, refutable vs irrefutable patterns¶
There is one other kind of pattern allowed in Haskell. It is called a lazy
pattern, and has the form ~pat
.
Lazy patterns are irrefutable: matching a value v
against ~pat
always
succeeds, regardless of pat. If an identifier in pat is later “used” on the
right-hand-side, and it didn’t match a value, it will return bottom _|_.
Refutable patterns are distinct from irrefutable ones in that they can fail to match.
Lazy patterns are useful in contexts where infinite data structures are being defined recursively.
Here’s an equation that defines the Fibonacci sequence:
fib@(1:tfib) = 1 : 1 : [ a+b | (a,b) <- zip fib tfib ]
This kind of equation is called a pattern binding because it is a top-level equation in which the entire left-hand side is a pattern.
The @
symbol here indicates we are using an as-pattern, where everything in
the parenthesis are captured by the name fib
.
Where else can you use patterns?¶
Patterns can appear in a lot of places. To be specific, patterns can be used with
lambda abstractions;
function definitions;
pattern bindings;
list comprehensions;
do expressions;
and case expressions.
Ultimately, the first five of these translate into case expressions. A good general rule of thumb is “if you can put a case expression inside of it, it probably supports pattern matching itself.”
7.4.1 Handling all the cases¶
Here’s a more pragmatic concern – if you write your patterns in the wrong order, your function will fail.
In the function isItTwo
the wildcard is always matched, and it wont ever
return True
for 2
:
isItTwo :: Integer -> Bool
isItTwo _ = False
isItTwo 2 = True
Here’s what happens if we load this into ghci and try:
·∾ :load is.hs
[1 of 1] Compiling Main ( is.hs, interpreted )
is.hs:3:1: warning: [-Woverlapping-patterns]
Pattern match is redundant
In an equation for ‘isItTwo’: isItTwo 2 = ...
|
3 | isItTwo 2 = True
| ^^^^^^^^^^^^^^^^
Ok, one module loaded.
·∾ isItTwo 8
False
·∾ isItTwo 2
False
This is called an overlapping pattern. To avoid this situation, try to order you patterns from most to least specific, particularly when using the wildcard.
Another issue is that of non-exhaustive patterns. This is when you don’t have patterns defined to deal with every case. (A partial function.) You patterns will then fail to match, and return bottom, which will throw an exception, that if unhandled will make your program fail.
Turn on -Wall
and GHC will warn you about overlapping and non-exhaustive
patterns. (Along with a bunch of other things!)
7.4.4 Exercises: Variety Pack¶
Given the following declarations
k (x, y) = x k1 = k ((4-1), 10) k2 = k ("three", (1 + 2)) k3 = k (3, True)
What is the type of
k
?I’m guessing that it should be
:: (x,y) -> x
; The tuple constructor matches term-level pretty well. Let’s see if I’m right:·∾ :type k k :: (a, b) -> a
What is the type of
k2
? Is it the same type ask1
ork3
?k2 :: String
;k1 :: Num a => a
;k3 :: Num a => a
; are my guesses. This would mean thatk2
has a different type thank1
andk3
. Let’s see:·∾ :type k2 k2 :: [Char]
Of
k1
,k2
,k3
, which will return the number3
as the result?Of course
k1
andk3
will, they both have3
as their first element:·∾ k3 3 ·∾ k1 3
Fill in the definition of the following function
f :: (a, b, c) -> (d, e, f) -> ((a, d), (c, f)) f = undefined
Sure.
f (a,_,c) (d,_,f) = ((a,d),(c,f))
. Is that right? Let’s test it:·∾ f :: (a, b, c) -> (d, e, f) -> ((a, d), (c, f)); f = undefined ·∾ f (a,_,c) (d,_,f) = ((a,d),(d,f)) ·∾ :type f f :: (a1, b1, c) -> (a2, b2, b3) -> ((a1, a2), (a2, b3)) ·∾ f (1,2,3) (4,5,6) ((1,4),(4,6))
It works!
7.5 Case expressions¶
Case expressions are control flow constructs that perform selection based on pattern matches.
Case expressions look like this:
value :: Value -> Integer
value card = case card of
Two -> 2
Three -> 3
Four -> 4
Five -> 5
Six -> 6
Seven -> 7
Eight -> 8
Nine -> 9
Ten -> 10
Jack -> 10
Queen -> 10
King -> 10
Ace -> 1
Each pattern -> expression pair is known as a match, but sometimes I’ll call these arms, instead.
7.5.1 Exercises: Case Practice¶
First, rewrite if-then-else expressions into case expressions.
The following should return
x
whenx
is greater thany
:functionC x y = if x > y then x else y
Rewritten:
functionC x y = case x > y of True -> x False -> y
Proof it works:
·∾ functionC x y = case x > y of { True -> x; False -> y } ·∾ functionC 3 8 8 ·∾ functionC 8 3 8 ·∾ functionC 1 9 9 ·∾ functionC 9 1 9 ·∾ functionC 10 1 10
The following will add
2
to even numbers and otherwise simply return the input value:ifEvenAdd2 n = if even n then (n+2) else n
Rewritten:
ifEvenAdd2 n = case even n of { True -> n+2; False -> n }
Proof it works:
·∾ ifEvenAdd2 n = if even n then n+2 else n ·∾ ifEvenAdd2 2 4 ·∾ ifEvenAdd2 8 10 ·∾ ifEvenAdd2 9 9 ·∾ ifEvenAdd2 n = case even n of { True -> n+2; False -> n } ·∾ ifEvenAdd2 2 4 ·∾ ifEvenAdd2 8 10 ·∾ ifEvenAdd2 9 9
The next exercise doesn’t have all the cases covered. See if you can fix it.
The following compares a value,
x
, to zero and returns an indicator for whetherx
is a positive number or negative number. What ifx
is0
?You may need to play with the compare function a bit to find what to do:
nums :: Num x => x -> Ordering nums x = case compare x 0 of LT -> -1 GT -> 1
Rewritten:
nums :: Num x => x -> Ordering nums x = case compare x 0 of LT -> -1 GT -> 1 EQ -> 0
Proof it works:
·∾ nums x = case compare x 0 of { LT -> -1; GT -> 1; EQ -> 0; } ·∾ nums 0 0 ·∾ nums 8 1 ·∾ nums (-9000) -1
Did you know case expressions can contain guards?
This is a bad example, but it shows the correct syntax.
Note that we are using ->
rather than =
to terminate the guard.
justPositiveOver12 mx =
case mx of
Just x | x > 12 -> Just x
| x <= 12 -> Nothing
_ -> Nothing
BNF of case expression taken from the 2010 Haskell Report, 3.13:
lexp -> case exp of { alts }
alts -> alt₁; ... ; altₙ (n >= 1)
alt -> pat -> exp [where decls]
| pat gdpat [where decls]
| (empty alternative)
gdpat -> guards -> exp [gdpat]
guards -> | guard₁, ..., guardₙ (n >= 1)
guard -> pat <- infixexp (pattern guard)
| let decls (local declaration)
| infixexp
Apparently, you can have let
expressions within guards. Neat.
If you don’t know BNF, I can recommend this guide to learn it:
https://www.ics.uci.edu/~pattis/ICS-33/lectures/ebnf.pdf.
7.6 Higher-order functions¶
Higher-order functions are functions that accept functions as arguments.
-- input function
-- that takes two args
-- vvvvvvvvvvvvv
flip :: (a -> b -> c) -> b -> a -> c
flip f x y = f y x
7.6.1 Exercises: Artful Dodgy¶
Given the following definitions tell us what value results from further applications. (For bonus points, fill in the type signatures of these functions.):
dodgy x y = x + y * 10
oneIsOne = dodgy 1
oneIsTwo = (flip dodgy) 2
Navigate to exercises/7.6.1_-_artful_dodgy.rst.d/dodgy/
and run stack
test
for proof.
dodgy 1 0
–>1
dodgy 1 1
–>11
dodgy 2 2
–>22
dodgy 1 2
–>21
dodgy 2 1
–>13
oneIsOne 1
–>11
oneIsOne 2
–>21
oneIsTwo 1
–>21
oneIsTwo 2
–>22
oneIsOne 3
–>31
oneIsTwo 3
–>23
7.7 Guards¶
7.7.2 Writing guard blocks¶
Here’s what a function with a guard block looks like:
myAbs x
| x < 0 = (-x)
| otherwise = x
The condition between |
and =
is a boolean expression. If it returns
True
, then the expression on the right of the =
is evaluated.
Guards always evaluate sequentially, so your guards should be ordered from the case that is most restrictive to the case that is least restrictive.
Surprise! Matches in case expressions can contain guards!
absoluteJust :: Maybe Int -> Maybe Int
absoluteJust n = case n of
Nothing -> Nothing
Just n
| n < 0 -> Just (-n)
| otherwise -> Just n
Note that otherwise
is just an another name for True
, provided by
Prelude to make guards more pleasant to read.
7.7.3 Guard Duty¶
To check my answers for yourself, navigate to the guard-duty directory and run stack test.
It is probably clear to you why you wouldn’t put an otherwise in your top-most guard, but try it with
avgGrade
anyway and see what happens. It’ll be more clear if you rewrite it as an otherwise match:| otherwise = 'F'
.Ok, my altered version, named
avgGradeUnconditional
, looks like this:avgGradeUnconditional x | otherwise = 'F' | y >= 0.9 = 'A' | y >= 0.8 = 'B' | y >= 0.7 = 'C' | y >= 0.59 = 'D' | y < 0.59 = 'F' where y = x / 100
What happens now if you pass a
90
as an argument?75
?60
?Since the first case is always evaluated, you will get something like this:
·∾ avgGradeUnconditional 90 'F' ·∾ avgGradeUnconditional 75 'F' ·∾ avgGradeUnconditional 60 'F'
What happens if you take
avgGrade
as it is written and reorder the guards? Does it still typecheck and work the same? Try moving| y >= 0.7 = 'C'
.My altered version, named
avgGradeReordered
looks like this:avgGradeReordered x | y >= 0.7 = 'C' | y >= 0.9 = 'A' | y >= 0.8 = 'B' | y >= 0.59 = 'D' | y < 0.59 = 'F' where y = x / 100
Pass it the argument
90
, which should be an'A'
. Does it return an'A'
?No, instead I get this:
·∾ avgGradeReordered 90 'C'
If I want to make the cuases independent of order, I can add a minimum bound to the range we’re checking, like this:
avgGradeOrderIndependent x | y >= 0.7 && y < 0.8 = 'C' | y >= 0.9 = 'A' | y >= 0.8 && y < 0.9 = 'B' | y >= 0.59 && y < 0.7 = 'D' | y < 0.59 = 'F' where y = x / 100
The following function returns
pal xs | xs == reverse xs = True | otherwise = False
xs
written backwards when it’sTrue
True when xs is a palindrome
False
whenxs
is a palindromeFalse
whenxs
is reversed
What types of arguments can pal take?
Let me think about it. Pal uses
(==)
andreverse
– what constraints do those functions add?·∾ :type (==) (==) :: Eq a => a -> a -> Bool ·∾ :type reverse reverse :: [a] -> [a]
…and it returns a
Bool
. So my guess ispal :: (Eq a) => [a] -> Bool
, which means that it can take anyEq
constrained list as an input.What is the type of the function pal?
I reasoned about this in the last answer, but let me check:
·∾ :type pal pal :: Eq a => [a] -> Bool
The following function returns
numbers x | x < 0 = -1 | x == 0 = 0 | x > 0 = 1
the value of its argument plus or minus 1
the negation of its argument
an indication of whether its argument is a positive or negative number or zero (This is basically
signum
.)binary machine language
What types of arguments can
numbers
take?Since
(==)
adds a constraint forEq
and(>)
addsOrd
and it works on numbers, the input type should be(Num a, Ord a) => a
, I think.What is the type of the function numbers?
My guess is
numbers :: (Ord a, Num a) => a -> a
(the(>)
operator addsOrd
). Let me check:·∾ :type numbers numbers :: (Ord a, Num a, Num p) => a -> p
Ok, so the result type is also
Num
constrained, but doesn’t require anOrd
instance.Let me think about that. … I’m not sure that makes sense. But if I do:
·∾ :{ ⋮ numbers :: (Ord a, Num a) => a -> a ⋮ numbers x | x < 0 = -1 ⋮ | x == 0 = 0 ⋮ | x > 0 = 1 ⋮ :} ·∾ :type numbers numbers :: (Ord a, Num a) => a -> a
It typechecks! This works since we’re making the result type less polymorphic.
7.8 Function composition¶
The compose operator is a type of higher-order function that allows us to combine function such that the result of applying one function gets passed to the next function as an argument.
Type signature and definition:
-- left right
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = f (g x)
infixr 9 .
(Ordinary function application has a precedence of 10 out of 10.)
Usage:
·∾ negate . sum $ [1..4]
-10
Parenthesis or $
are needed to separate the group of composed functions and
the argument they’ll operate on.
7.9 Pointfree style¶
Pointfree refers to a style of composing functions without specifying their arguments. The “point” in “pointfree” refers to the arguments, not the composition operator.
The idea is to put an emphasis on series of transformations by eliding temporary name bindings. In combination with compose, this feels reminiscent of unix shell pipelines (but in reverse). Use this only where it makes sense. If keeping track of parameter names is getting confusing, maybe that’s a good candidate for pointfree.
A simple example:
-- pointful
f z xs = foldr (+) z xs
-- pointfree
f = foldr (+)
g = negate . sum
Another one:
·∾ f = length . filter (== 'a')
·∾ f "abracadabra"
5
Whenever I write pointfree code, it feels satisfying. But when I read other people pointfree code, I hate it.
7.11 Chapter Exercises¶
7.11.1 Multiple choice¶
A polymorphic function
changes things into sheep when invoked
has multiple arguments
has a concrete type
may resolve to values of different types, depending on inputs
Two functions named
f
andg
have typesChar -> String
andString -> [String]
respectively. The composed functiong . f
has the typeChar -> String
Char -> [String]
Proof:
·∾ f :: Char -> String; f = undefined ·∾ g :: String -> [String]; g = undefined ·∾ :type g . f g . f :: Char -> [String]
[[String]]
Char -> String -> [String]
A function
f
has the typeOrd a => a -> a -> Bool
and we apply it to one numeric value. What is the type now?Ord a => a -> Bool
Proof:
·∾ f :: Ord a => a -> a -> Bool; f = undefined ·∾ :type f 1 f 1 :: (Ord a, Num a) => a -> Bool
Num -> Num -> Bool
Ord a => a -> a -> Integer
(Ord a, Num a) => a -> Bool
A function with the type
(a -> b) -> c
requires values of three different types
is a higher-order function
must take a tuple as its first argument
has its parameters in alphabetical order
Given the following definition of
f
, what is the type off True
?f :: a -> a f x = x
f True :: Bool
Proof:
·∾ :{ ⋮ f :: a -> a ⋮ f x = x ⋮ :} ·∾ :type f True f True :: Bool
f True :: String
f True :: Bool -> Bool
f True :: a
7.11.2 Let’s write code¶
The following function returns the tens digit of an integral argument.
tensDigit :: Integral a => a -> a tensDigit x = d where xLast = x `div` 10 d = xLast `mod` 10
First, rewrite it using
divMod
.Here you go!
tensDigit' x = let (y,_) = x `divMod` 10 in y `mod` 10
Does the
divMod
version have the same type as the original version?Yes, here’s the proof:
·∾ :type tensDigit tensDigit :: Integral a => a -> a ·∾ :type tensDigit' tensDigit' :: Integral a => a -> a
Next, let’s change it so that we’re getting the hundreds digit instead. You could start it like this (though that may not be the only possibility):
hunsD x = d2 where d = undefined ...
Ok, here is my attempt
hunsD x = (x `div` 100) `mod` 10
Implement the function of the type
a -> a -> Bool -> a
once each using a case expression and once with a guard.{-# ANN foldBool ("HLint: ignore Use if" :: String) #-} foldBool :: a -> a -> Bool -> a foldBool x y b = case b of { False -> x; True -> y }
Fill in the definition
g :: (a -> b) -> (a, c) -> (b, c) g = undefined
Alright, here we go.
g :: (a -> b) -> (a, c) -> (b, c) g aToB (a, c) = (aToB a, c)
Write the following code into a source file. Then load it and run it in GHCi to make sure you understand why the evaluation results in the answers you see.
-- arith4.hs module Arith4 where -- id :: a -> a -- id x = x roundTrip :: (Show a, Read a) => a -> a roundTrip a = read (show a) main = do print (roundTrip 4) print (id 4)
Next, write a pointfree version of
roundTrip
. (n.b., This refers to the function definition, not to its application in main.)roundTrip' = read . show
I’m pretty sleep deprived and don’t understand this question.