1.3 presents the following higher-order function:
It’s worth noting that
sum is not as general as it could be. It hardwires in a terminal value of 0, and the
> operator & conditional. Its type would be something like
sum :: (Num a) => (a -> a) -> a -> (a -> a) -> a -> a; a more general approach would be to require a general boolean function rather than a particular value.
iterate is similar, but note that it has a type signature that initially seems simpler and less powerful:
iterate :: (a -> a) -> a -> [a]. Surely the more parameterizable a function, the more general it is?
iterate has but 1 function argument, while
sum has 2.
iterate has 2 arguments in a sense, when we remember how it generates an infinite list—we can think of it as the zeroth entry is
x, the first entry
f x, the 2nd entry is
f (f x), the 3rd entry is
f (f (f x)), and so on1. The missing function is whatever is calling
iterate and using its values; in
sum, the termination is explicitly handled by a function because the evaluation scheme in Scheme is strict.
Here’s an example. If we called
iterate (^3) 2, we would get the infinite list
sum clearly has a stopping case. So we compose
iterate with takeWhile:
iterate gets the seed value and the knowledge of how to keep changing the seed value, and
takeWhile gets the knowledge of when the seed value has changed enough.
This will be useful for approximation. But note that it’s not useful for straight summing like
iterate uses the passed-in function to generate the next target from the old target, while
sum has the passed-in function doing something else entirely (moving from old to new is hardwired to use the function
(+1)). So if we wrote the obvious Haskell
sumCube definitions, they would be utterly wrong:
sumCube 1 10 will loop because , and if we tried
sumCube 2 10, it’d still be wrong—we’d get
[2,8] because the test will return false for .
sum, we need to pay attention to what dependencies are where. Given the parameters
a b, we can get the targets with just
[a..b]; then, we take whatever function and
map it onto the targets:
foo f a b = map f [a..b]; then we fold the resulting list into a final answer:
foo f a b = sum (map f [a..b]), and finally:
sum has 4 parameters, not 3. The
[a..b] hides the
Pseudo-code from the specification:
The tricky part is the middle of the summation. We need to multiply by 2 or 4 except for the first and last terms. Kind of hard to express that as a simple recursion. We could generate that middle directly by generating the indices (
y on them (
map y), creating an infinite list of coefficients (
cycle [2,4]), and combining the coefficients with the transformed indices (
zipWith (*)), and then directly specify the
y 0 and
y n calls so we have something like:
Ugly, though it works, for example
(This has diminishing returns; a n of 1000000 gives the three-places more precise 0.24999966666716916 but is much slower.)
If we didn’t want to use the various Prelude operators in a Scheme version, we’d have to drag around state in the form of a variable.
Interestingly, Dr. Scheme evaluates
(simpson-integral cube 0 1 1000) to . I don’t know how it can be so precise.
Ought to be straightforward by this point (especially since half the battle is figuring out the auxiliary function
iter a result):
sum rewritten for multiplication is of course (modulo better variable names):
All we had to change was the ‘seed’ and the inner function (
factorial requires a little imagination, but here Haskell prevents me from having to think too much; Haskell’s product has the type signature
Num a => [a] -> a, so immediately the obvious way to define
factorial is to generate a list of the right length:
How can we generate such a list with this Scheme function? We have to pass in 2 functions and 2 integers which will somehow do this.
The answer is to make our first function do nothing, and the second function just increment the seed; an example:
(lambda (a) a) is bound to the variable
f, which gets called on the first multiplication, but does nothing—exactly as needed. Then
(lambda (g) (+ g 1)) takes care of generating the next entry in the Scheme equivalent of
A worldly Haskeller will look at this and immediately think, ‘an initial seed value, and some way of combining values… could this idea be a monoid‽’
As sigfpe’s excellent tutorial or Learn You A Haskell or Real World Haskell will tell us, yes—this is a monoid. In fact, integers (our topic) form two different monoids, one for addition (Sum) and one for multiplication (Product).
Monoid typeclass requires the following:
mempty is our ‘seed’ value—it is the identity. For Sum, ; for Product, .
mappend is how to combine 2 such monoids. Both
(*) have the right type signature:
Num a => a -> a -> a.
mconcat can be automatically defined from
mappend—you just prefix a
mempty onto the list, and then proceed to
mappend your way down it.
This works out perfectly for
mconcat [1..5] -- for Product ~> fold [mempty, 1, 2, 3, 4, 5] -- not the actual fold; artistic license ~> mempty `mappend` 1 `mappend` 2 `mappend` 3 `mappend` 4 `mappend 5 ~> 1 `mappend` 1 `mappend` 2 `mappend` 3 `mappend` 4 `mappend 5 ~> 1 * 1 * 2 * 3 * 4 * 5 ~> 120 mconcat [1..5] -- for Sum ~> fold [mempty, 1, 2, 3, 4, 5] -- not the actual fold; artistic license ~> mempty `mappend` 1 `mappend` 2 `mappend` 3 `mappend` 4 `mappend 5 ~> 0 `mappend` 1 `mappend` 2 `mappend` 3 `mappend` 4 `mappend` 5 ~> 0 + 1 + 2 + 3 + 4 + 5 ~> 15
Notice that right up to the final steps everything was the same.
mconcat is basically our
product but abstracted away from a specific monoid and how to generate the
A great many data structures or datatypes are monoids; they’re worth knowing about.
(accumulate combiner null-value term a next b)
accumulatetakes as arguments the same term and range specifications as
product, together with a combiner procedure (of two arguments) that specifies how the current term is to be combined with the accumulation of the preceding terms and a null-value that specifies what base value to use when the terms run out. Write
accumulateand show how
productcan both be defined as simple calls to
Then we define
product by specializing
accumulate in its first 2 arguments:
combiner takes 2 arguments of the same data type (such as
Int) and produces a third argument of the same data type; that is,
combiner :: a -> a -> a, or since we just covered monoids, we’ll say (Monoid a) => a -> a -> a. The first hit is
Data.Monoid mappend :: Monoid a => a -> a -> a.
The null-value is simply that same data type, (Monoid a) => a; the first hit is
Data.Monoid mempty :: Monoid a => a
With an instance of the Monoid typeclass, we can scrap the null-value and
combiner arguments since they are pre-defined. We just need the range (
b) and how to increment (
next) since the Monoid typeclass doesn’t cover that. A fairly literal translation of the previous section:
accumulate :: (Ord a, Monoid a1) => (a -> a1) -> a -> (a -> a) -> a -> a1 accumulate term a next b = let iter a' result = if a' > b then result else iter (next a') (term a' `mappend` result) in iter a mempty product :: (Ord a1, Num a) => (Product a1 -> Product a) -> a1 -> (Product a1 -> Product a1) -> a1 -> a product a b c d = getProduct (accumulate a (Product b) c (Product d)) sum :: (Ord a1, Num a) => (Sum a1 -> Sum a) -> a1 -> (Sum a1 -> Sum a1) -> a1 -> a sum a b c d = getSum (accumulate a (Sum b) c (Sum d))
Usage would look like
(This example evaluates to 15, and is equivalent to
I cheat a little here, and just rewrite
combiner to get the filter effect:
Again we specialize (assuming the obvious definitions for
The remarks on scoping and how lambda is what lurks behind the sugar of
let are true in Haskell as far as I know, and thus a bit boring to me.
We’re asked to evaluate
(f f) given:
Quickly eyeballing it, I assumed it would turn out to be an infinite loop, as it became something like
(f (f (f (f (f...))))). But when I calculate it out by hand, I realize that is not the case!
(lambda (x) (x 2) f)
(lambda (x) (x 2) 2)
- Dr. Scheme error: “procedure application: expected procedure, given: ‘2’; arguments were: ‘2’”
This is the idea; the actual implementation is more syntactically efficient:
iterate f x = x : iterate f (f x).↩︎