I'm new to functional programming.
So the terms cons appends an element to the front of the list. Where
cons ≜ λx:λl:λc:λn: c x (l c n)
How should I go about proving that cons works correctly using beta reduction for a sample function call? For example reducing cons 3 [2,1] to [3,2,1]?
Is there a formula like for the arithmetic operations in lambda calculus? I'm a bit confused on how to approach this compared to an arithmetic operation (i.e. addition or multiplication).
Thanks.
cons ≜ λx:λl:λc:λn: c x (l c n) means that
cons x l c n =
c x (l c n)
(in functional / applicative / combinatory notation). So
cons 3 [2,1] c n =
= c 3 ([2,1] c n)
and what is [2,1] if not just shortcut notation for cons 2 [1] so that we continue
= c 3 (cons 2 [1] c n)
= c 3 (c 2 ([1] c n))
= c 3 (c 2 (cons 1 [] c n))
= c 3 (c 2 (c 1 ([] c n)))
So there's no reduction from cons 3 [2,1] to [3,2,1]; [3,2,1] is cons 3 [2,1]. And [2,1] is cons 2 [1], and [1] is cons 1 [].
The list cons x xs, when supplied with c and n arguments, will turn into c x (xs c n), and so will xs, in its turn; so any list's elements are used in the chain of applications of c on top one another.
And what should [] c n turn into? It has nothing in it to put through the c applications -- those are to be applied to a list's elements, and [] has none. So the only reasonable thing to do (and I'm sure you're already given that definition) is to turn [] c n into just n:
cons 3 [2,1] c n =
= c 3 (c 2 (c 1 ([] c n)))
= c 3 (c 2 (c 1 n ))
whatever c and n are.
And that's that.
Related
I would appreciate any help with problem bellow. Even a direction of what I should read about. Thank you!
I need to create a a graph (tree) given a set of rules which indicates the relationship between vertices, where the relationship does not have to be direct.
Constraints:
Vertex rules should be met. In the example bellow, A<-D indicates that A is the parent of D, though it doesn't have to be a direct parent.
The distance between vertices should be as low as possible.
Each vertex can only have one parent, therefore the outcome will be a tree.
Example set of rules: [A<-D, D<-E, E<-F, A<-B, B<-C, E<-C, F<-C]
First image is a possible outcome, but it is not the best one given the distance between vertex C to F and C to E is too far. Therefore a better solution is the 2nd image.
You have the rules:
The distance between vertices should be as low as possible; and
Each vertex can only have one parent.
and the syntax:
A<-D indicates that A is an ancestor of D.
(Note: the change of terminology to ancestor.)
Then for the graph generated by A<-D, D<-E, E<-F, A<-B, B<-C, E<-C, F<-C
This gives 3 paths from A to C (using the syntax (X -> Y) as X is the [direct] parent of Y):
(A -> D), (D -> E), (E -> C) and
(A -> D), (D -> E), (E -> F), (F -> C) and
(A -> B), (B -> C)
However, by the 2nd rule ("Each vertex can only have one parent") then there must only be a single path from A to C which passes through D, E, F (in that order) and also through B so the possible paths have the order A,D,E,F,C with B inserted somewhere:
Path 1: (A -> B), (B -> D), (D -> E), (E -> F), (F -> C)
Path 2: (A -> D), (D -> B), (B -> E), (E -> F), (F -> C)
Path 3: (A -> D), (D -> E), (E -> B), (B -> F), (F -> C)
Path 4: (A -> D), (D -> E), (E -> F), (F -> B), (B -> C)
You can then calculate the distances for each of the rules (assuming a constant distance between each vertex):
Rule
Path1
Path2
Path3
Path4
A<-D
2
1
1
1
D<-E
1
2
1
1
E<-F
1
1
1
2
A<-B
1
2
3
4
B<-C
4
3
2
1
E<-C
2
2
3
3
F<-C
1
1
1
2
Total
12
12
12
12
Since the total distance for all the paths is identical then any/all of the four solutions is equally valid against your rules.
According to the book, this is what the function definition is,
The function scramble takes a non-empty tuple in which no argument is greater than its own index and returns a tuple of same length. Each number in the argument is treated as a backward index from its own position to a point earlier in tuple. The result at each position is obtained by counting backward from the current position according to this index.
And these are some examples,
; Examples of scramble
(scramble '(1 1 1 3 4 2 1 1 9 2)) ; '(1 1 1 1 1 4 1 1 1 9)
(scramble '(1 2 3 4 5 6 7 8 9)) ; '(1 1 1 1 1 1 1 1 1)
(scramble '(1 2 3 1 2 3 4 1 8 2 10)) ; '(1 1 1 1 1 1 1 1 2 8 2)
Here is the implementation,
(define pick
(λ (i lat)
(cond
((eq? i 1) (car lat))
(else (pick (sub1 i)
(cdr lat))))))
(define scramble-b
(lambda (tup rev-pre)
(cond
((null? tup) '())
(else
(cons (pick (car tup) (cons (car tup) rev-pre))
(scramble-b (cdr tup)
(cons (car tup) rev-pre)))))))
(define scramble
(lambda (tup)
(scramble-b tup '())))
This is a case where using a very minimal version of the language means that the code is verbose enough that understanding the algorithm is not perhaps easy.
One way of dealing with this problem is to write the program in a much richer language, and then work out how the algorithm, which is now obvious, is implemented in the minimal version. Let's pick Racket as the rich language.
Racket has a function (as does Scheme) called list-ref: (list-ref l i) returns the ith element of l, zero-based.
It also has a nice notion of 'sequences' which are pretty much 'things you can iterate over' and a bunch of constructs whose names begin with for for iterating over sequences. There are two functions which make sequences we care about:
in-naturals makes an infinite sequence of the natural numbers, which by default starts from 0, but (in-naturals n) starts from n.
in-list makes a sequence from a list (a list is already a sequence in fact, but in-list makes things clearer and there are rumours also faster).
And the iteration construct we care about is for/list which iterates over some sequences and collects the result from its body into a list.
Given these, then the algorithm is almost trivial: we want to iterate along the list, keeping track of the current index and then do the appropriate subtraction to pick a value further back along the list. The only non-trivial bit is dealing with zero- vs one-based indexing.
(define (scramble l)
(for/list ([index (in-naturals)]
[element (in-list l)])
(list-ref l (+ (- index element) 1))))
And in fact if we cause in-naturals to count from 1 we can avoid the awkward adding-1:
(define (scramble l)
(for/list ([index (in-naturals 1)]
(element (in-list l)))
(list-ref l (- index element))))
Now looking at this code, even if you don't know Racket, the algorithm is very clear, and you can check it gives the answers in the book:
> (scramble '(1 1 1 3 4 2 1 1 9 2))
'(1 1 1 1 1 4 1 1 1 9)
Now it remains to work out how the code in the book implements the same algorithm. That's fiddly, but once you know what the algorithm is it should be straightforward.
If the verbal description looks vague and hard to follow, we can try following the code itself, turning it into a more visual pseudocode as we go:
pick i [x, ...ys] =
case i {
1 --> x ;
pick (i-1) ys }
==>
pick i xs = nth1 i xs
(* 1 <= i <= |xs| *)
scramble xs =
scramble2 xs []
scramble2 xs revPre =
case xs {
[] --> [] ;
[x, ...ys] -->
[ pick x [x, ...revPre],
...scramble2 ys
[x, ...revPre]] }
Thus,
scramble [x,y,z,w, ...]
=
[ nth1 x [x] (*x=1..1*)
, nth1 y [y,x] (*y=1..2*)
, nth1 z [z,y,x] (*z=1..3*)
, nth1 w [w,z,y,x] (*w=1..4*)
, ... ]
Thus each element in the input list is used as an index into the reversed prefix of that list, up to and including that element. In other words, an index into the prefix while counting backwards, i.e. from the element to the left, i.e. towards the list's start.
So we have now visualized what the code is doing, and have also discovered requirements for its input list's elements.
Just for fun (Project Euler #65) I want to implement the formula
n_k = a_k*n_k-1 + n_k-2
in an efficient way. a_k is either 1 or (* 2 (/ k 3)), depending on k.
I started with a recursive solution:
(defun numerator-of-convergence-for-e-rec (k)
"Returns the Nth numerator of convergence for Euler's number e."
(cond ((or (minusp k)) (zerop k) 0)
((= 1 k) 2)
((= 2 k) 3)
((zerop (mod k 3)) (+ (* 2 (/ k 3) (numerator-of-convergence-for-e-rec (1- k)))
(numerator-of-convergence-for-e-rec (- k 2))))
(t (+ (numerator-of-convergence-for-e-rec (1- k))
(numerator-of-convergence-for-e-rec (- k 2))))))
which works for small k but gets pretty slow for k = 100, obviously.
I have no real idea how to transform this function to a version with could be tail-call optimized. I have seen a pattern using two accumulating variables for fibonacci numbers but fail to transform this pattern to my function.
Is there a general guideline how to transform complex recursions to tco versions or should I implement an iterative solution directly.?
First, note that memoization is probably the simplest way optimize your code: it does not reverse the flow of operations; you call your function with a given k and it goes back to zero to compute the previous values, but with a cache. If however you want to turn your function from recursive to iterative with TCO, you'll have to compute things from zero up to k and pretend you have a constant-sized stack / memory.
Step function
First, write a function which computes current n given k, n-1 and n-2:
(defun n (k n1 n2)
(if (plusp k)
(case k
(1 2)
(2 3)
(t (multiple-value-bind (quotient remainder) (floor k 3)
(if (zerop remainder)
(+ (* 2 quotient n1) n2)
(+ n1 n2)))))
0))
This step should be easy; here, I rewrote your function a little bit but I actually only extracted the part that computes n given the previous n and k.
Modified function with recursive (iterative) calls
Now, you need to call n from k starting from 0 to the maximal value you want to be computed, named m hereafter. Thus, I am going to add a parameter m, which controls when the recursive call stops, and call n recursively with the modified arguments. You can see the arguments are shifted, current n1 is the next n2, etc.
(defun f (m k n1 n2)
(if (< m k)
n1
(if (plusp k)
(case k
(1 (f m (1+ k) 2 n1))
(2 (f m (1+ k) 3 n1))
(t (multiple-value-bind (quotient remainder) (floor k 3)
(if (zerop remainder)
(f m (1+ k) (+ (* 2 quotient n1) n2) n1)
(f m (1+ k) (+ n1 n2) n1)))))
(f m (1+ k) 0 n1))))
That's all, except that you don't want to show this interface to your user. The actual function g properly bootstraps the initial call to f:
(defun g (m)
(f m 0 0 0))
The trace for this function exhibits an arrow ">" shape, which is the case with tail-recursive functions (tracing is likely to inhibit tail-call optimization):
0: (G 5)
1: (F 5 0 0 0)
2: (F 5 1 0 0)
3: (F 5 2 2 0)
4: (F 5 3 3 2)
5: (F 5 4 8 3)
6: (F 5 5 11 8)
7: (F 5 6 19 11)
7: F returned 19
6: F returned 19
5: F returned 19
4: F returned 19
3: F returned 19
2: F returned 19
1: F returned 19
0: G returned 19
19
Driver function with a loop
The part that can be slightly difficult, or make your code hard to read, is when we inject tail-recursive calls inside the original function n. I think it is better to use a loop instead, because:
unlike with the tail-recursive call, you can guarantee that the code will behave as you wish, without worrying whether your implementation will actually optimize tail-calls or not.
the code for the step function n is simpler and only expresses what is happening, instead of detailing how (tail-recursive calls are just an implementation detail here).
With the above function n, you can change g to:
(defun g (m)
(loop
for k from 0 to m
for n2 = 0 then n1
for n1 = 0 then n
for n = (n k n1 n2)
finally (return n)))
Is there a general guideline how to transform complex recursions to
tco versions or should I implement an iterative solution directly?
Find a step function which advances the computation from the base case to the general case, and put intermediate variables as parameters, in particular results from past calls. This function can call itself (in which case it will be tail-recursive, because you have to compute all the arguments first), or simply called in a loop. You have to be careful when computing the initial values, you might have more corner cases than with a simple recursive function.
See also
Scheme's named let, the RECUR macro in Common Lisp and the recur special form in Clojure.
As a test for one of my classes, our teacher asked us to test a recursive and non-recursive approach to the famous Euclidean Algorithm:
Iterative
(defun gcdi (a b)
(let ((x a) (y b) r)
(while (not (zerop y))
(setq r (mod x y) x y y r))
x))
Recursive
(defun gcdr (a b)
(if (zerop b)
a
(gcdr b (mod a b))))
And then I ran a test:
(defun test-iterative ()
(setq start (float-time))
(loop for x from 1 to 100000
do (gcdi 14472334024676221 8944394323791464)) ; Fibonacci Numbers close to 2^64 >:)
(- (float-time) start))
(defun test-recursive ()
(setq start (float-time))
(loop for x from 1 to 100000
do (gcdr 14472334024676221 8944394323791464)) ; Fibonacci Numbers close to 2^64 >:)
(- (float-time) start))
And then I ran the timers:
(test-recursive)
: 1.359128475189209
(test-iterative)
: 1.7059495449066162
So my question is this, why did the recursive test perform faster than the iterative test? Isn't iterative almost always better than recursion? Is elisp an exception to this?
The theoretical answer is that the recursive version is actually tail
recursive and thus should compile to iteration.
However, disassembling
the functions reveals the truth:
byte code for gcdi:
args: (a b)
0 varref a
1 varref b
2 constant nil
3 varbind r
4 varbind y
5 varbind x
6 varref y
7:1 constant 0
8 eqlsign
9 goto-if-not-nil 2
12 constant mod
13 varref x
14 varref y
15 call 2
16 varset r
17 varref y
18 varset x
19 varref r
20 dup
21 varset y
22 goto 1
25:2 varref x
26 unbind 3
27 return
vs
byte code for gcdr:
args: (a b)
0 varref b
1 constant 0
2 eqlsign
3 goto-if-nil 1
6 varref a
7 return
8:1 constant gcdr
9 varref b
10 constant mod
11 varref a
12 varref b
13 call 2
14 call 2
15 return
You can see that the gcdr has almost half as many instructions, but contains two call instructions, which means that ELisp does not, apparently, convert the tail recursive call to iteration.
However, function calls in ELisp are relatively cheap and
thus the recursive version executes faster.
PS. While the question makes sense, and the answer is actually generally applicable (e.g., the same approach is valid for Python and CLISP, inter alia), one should be aware that choosing the right algorithm (e.g., linearithmic merge-sort instead of quadratic bubble-sort) is much more important than "micro-optimizations" of the implementation.
Hmm... indeed that's weird, since Emacs's implementation of function calls (and hence recursion) is not very efficient.
I just evaluated the code below:
(defun sm-gcdi (a b)
(let ((x a) (y b) r)
(while (not (zerop y))
(setq r (mod x y) x y y r))
x))
(defun sm-gcdr (a b)
(if (zerop b)
a
(sm-gcdr b (mod a b))))
(defun sm-test-iterative ()
(let ((start (float-time)))
(dotimes (_ 100000)
(sm-gcdi 14472334024676221 8944394323791464))
(- (float-time) start)))
(defun sm-test-recursive ()
(let ((start (float-time)))
(dotimes (_ 100000)
(sm-gcdr 14472334024676221 8944394323791464))
(- (float-time) start)))
and then tried M-: (sm-test-recursive) and M-: (sm-test-iterative) and sure enough the iterative version is faster for me. I then did M-: (byte-compile 'sm-gcdi) and M-: (byte-compile 'sm-gcdr) and tried again, and the speed difference was even larger.
So your measurements come as a surprise to me: they don't match my expectations, and don't match my tests either.
I'm working on a very simple OCaml exercise on CPS. Line 8-10 is to convert two recursive calls into one tail recursion. However, the compiler complains the type of Line 8:
File "tmp.ml", line 8, characters 9-14:
Error: This expression has type int -> int -> (int -> int) -> int
but an expression was expected of type int
I understand that the compiler expects an int at line 8 because line 6 returns an int. But can someone illustrate why the type of Line 8-10 is not an int?
4 let rec f i n k (i:int) (n:int) (k:int->int) :int =
5 if i + n < 0 then
6 k 1
7 else
8 (f i (n-1) (fun v ->
9 f (i-1) n (fun vv->
10 k (v + vv))))
11 in f 1 1 (fun x -> x)
f i n-1 is parsed as (f i n)-1 rather than the f i (n-1) you presumably expect.
Additionally,
let rec f i n k (i:int) (n:int) (k:int->int) :int
means that your function takes 6 arguments: i, n, k, i, n and k. You probably meant to write:
let rec f (i:int) (n:int) (k:int->int) :int