By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.

Again we start with main module boilerplate:

:- module p2.

:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

In our implementation, we will again use the

`int`

module. Because the range of terms to sum is not definite (we do not know how many terms are less than four million without prior analysis), `solutions`

will not be useful to us. (Note that even the `solutions.do_while`

predicate cannot be relied upon, as the order in which terms are generated is not guaranteed, and we have no efficient way of knowing if we’ve found all the terms of interest.):- implementation.

:- import_module int.

First let’s define the recursive function

`fibonacci`

of one argument to compute the `I`

th Fibonacci number.:- func fibonacci(int) = int.

:- pragma memo(fibonacci/1).

fibonacci(I) =

(if I = 0 then 1

else if I = 1 then 2

else fibonacci(I - 1) + fibonacci(I - 2)).

Normally a function so defined would be exponential with

`I`

. However, by instructing the Mercury compiler to memoize `fibonacci`

via the `memo`

pragma, we achieve linear performance as if we had used a dynamic programming algorithm. (Note that memoization is not yet available in all compilation grades, including Java.)To perform the summation, we can define a recursive function which recurs on successive values of

`I`

until `fibonacci(I)`

is not less than four million:sum_fibonaccis(I) =

(if fibonacci(I) @ Fibo < 4000000 then

sum_fibonaccis(I + 1) +

(if even(Fibo) then Fibo else 0)

else 0).

The

`@`

function is used to bind `fibonacci(I)`

to `Fibo`

in an expression context (akin to C’s `=`

operator). The built-in predicate `int.even`

is used to test for evenness.Those readers familiar with functional programming will likely notice that this function is not tail-recursive; i.e., the result of the recursive call is not returned directly. This will cause unnecessary (though, in this case, benign) stack growth. The typical solution is to rewrite

`fibonacci`

with an accumulator argument:sum_fibonaccis_acc(I, Sum) =

(if fibonacci(I) @ Fibo < 4000000 then

sum_fibonaccis_acc(I + 1, Sum +

(if even(Fibo) then Fibo else 0))

else Sum).

Mercury however is able to automatically perform this transformation, provided we inform it that

`(Fibo0 + Fibo1 + ...) + FiboN`

is indeed the same as `Fibo0 + (Fibo1 + ... + FiboN)`

– that is, that addition is associative::- promise all [A, B, C, ABC] (A + B) + C = ABC <=> A + (B + C) = ABC.

This optimization is enabled with the

`--introduce-accumulators`

option to `mmc`

. The compiler unfortunately can recognize only a narrow set of functions and predicates as candidates for accumulator introduction. In particular, altering our definition of `sum_fibonaccis`

to bind `Fibo = fibonacci(I)`

in a separate body instead of using `@`

, or moving the innermost `if`

to outside the recursive call, causes the compiler not to recognize this function as a candidate for accumulator introduction.The remainder of our implementation is unremarkable:

p2 = sum_fibonaccis(0).

main --> print(p2), nl.

Compiling the program with

`mmc --infer-all --introduce-accumulators --make p2`

produces a large warning concerning possible performance problems due to the accumulator introduction altering the order of summation. This is obviously not a problem for addition and can be safely ignored; the warning is meant for operations such as linked list concatenation, the performance of which depends on the value of one operand more than that of the other. If desired this warning can be disabled with the `--inhibit-accumulator-warnings`

option.The resulting program runs in about 100 ms on my dual-core 1.6 GHz machine.

Download p2.m

## No comments:

## Post a Comment