1. Trang chủ
  2. » Công Nghệ Thông Tin

Recursion and Constructing Combinators An Intermediate+n

13 2 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Nội dung

recursion pdf Recursion and Constructing Combinators An Intermediate+n Tutorial by Jonathan Carlos Baca 1 What Recusion Is and Is Not This is a tutorial that will not teach you how to use a certain li.

Recursion and Constructing Combinators An Intermediate+n Tutorial by Jonathan Carlos Baca What Recusion Is and Is Not This is a tutorial that will not teach you how to use a certain library, popular programming technique, or even make you a faster coder However, I maintain that this tutorial will make you a better coder; code is logic knit into forms by human fingertips dancing on keyboards, and although you may just want to dab, why not learn ballet as well? For this tutorial to help you out, you must know what recursion is This is actually pretty simple Suppose we have a function called ‘foo’, defined as follows (I’m using Javascript, so haters; bear along For some of you, I’ve also written the tutorial in Lisp, so if you’re that type of purist, you’ll be pretty happy For beginners: the first examples are always in Javascript, and the second ones are in Lisp, but you’ll get a feel for which is which almost immediately): Javascript function foo(n) { if(n == 0) { return n; } return + foo(n - 1); } JavaScript Lisp (defun foo (n) (if (zerop n) n (1+ (foo (1- n))))) What output will foo(3) produce? Well let’s walk through it if n:=3, then n == is false, so it will return + foo(2) So what’s foo(2)? In the same manner, it’s + foo(1) What’s foo(1)? Again, + foo(0) Now what’s foo(0)? Since here n:=0, we return n (which is 0), so foo(0) = So + foo(0) = foo(1) = 1, + foo(1) = foo(2) = 2, and + foo(3) = foo(3) = If you haven’t figured it out yet, it just takes a positive integer and regurgitates it The question I’m posing seems like a simple one: can recursion exist without functions being able to refer back to themselves? This may seem like an obvious ‘no’ to some of you, ‘yes’ to others, and to those of you in the back (who should give the other students a chance) shouting about the Y-combinator: shut up a minute Let me make my meaning more clear: suppose we’re given a computer language (I’ve chosen javascript to write this tutorial in, although any functionalish language will do) that has names for variables and functions and has a mechanism for passing those functions and variables into other functions The question is this: ‘Must a function refer to itself by the name it was given upon definition to calculate recursive functions in a more-or-less direct way?’ It’s a nebulous question, to be sure Ouroboros (the snake eating its own tail) Suppose we want to recurse without naming the recursor We can start by writing a simple recursive function I’ve chosen multiplication The identity we’ll be using is * b = 0, but if a != 0, then a * b = (1 + (a - 1)) * b = b + (a - 1) * b (or, in English, zero times anything is zero, and thing times second thing is equal to second thing plus first thing minus one So * = + (2 * 10), and so on: Javascript function multiply(a, b) { if(a == 0) { return 0; } return b + multiply(a-1, b); } JavaScript Lisp (defun multiply (a b) (if (zerop a) (+ b (multiply (1- a) b)))) We could write this more succinctly (and without structures) as: Javascript function multiply(a, b) { return a? b + multiply(a-1, b) : 0; } JavaScript Now suppose we have a function called ‘doMultiply’, which does everything that multiply does, but has to be explicitly given itself as an argument in order to recurse using itself We could something like this: Javascript function return } function return } doMultiply(dm, a, b) { a? b + dm(dm, a-1, b) : 0; JavaScript multiply(a, b) { doMultiply(doMultiply, a, b); Lisp (defun do-multiply (dm a b) (if (zerop a) (+ b (funcall dm a b)))) (defun multiply (a b) (do-multiply #'do-multiply a b)) And to tidy things up, I’ve introduced a ‘recursor’ functino that just supplies a function with itself and its ‘base’ arguments: Javascript function return } function return } function return } doMultiply(dm, a, b) { a ? b + dm(dm, a-1, b) : 0; JavaScript recursor(fxn, a, b) { fxn(fxn, a, b); multiply(a, b) { recursor(doMultiply, a, b); Lisp 10 (defun do-multiply (dm a b) (if (zerop a) (+ b (funcall dm dm (1- a) b)))) (defun recursor (f a b) (funcall f f a b)) (defun multiply (a b) (recursor #'do-multiply a b)) In javascript, there’s a tidy little notation that I like to call parenfuncs or ( )=>….-style expressions The way these work is that you have a list of arguments followed by a block or expression For the sake of beauty, the only operator we’re going to allow is the ?: triadic operator (where a?b:c yields b if a evaluates to true/not-false, and b otherwise), and we’re not going to allow any brackets Our function is simple enough not to need a lot of reconfiguring, and the functions above could be defined as such: Javascript var doMultiply = (dm, a, b) => a ? b + dm(dm, a-1, b) : 0; var recursor = (fxn, a, b) => fxn(fxn, a, b); var multiply = (a, b) => recursor(doMultiply, a, b); JavaScript Now the beauty of the parenfuncs (especially with the guidelines I set forth) is that you can replace any occurrence of a named parenfunc with the goop inside it For example, if we’re given: Javascript var bar = (n) => n * n; var foo = (a, b) => bar(a - 1) - b; JavaScript We can replace the second bar with what’s left of the =>, substituting ‘a - 1’ for ‘n’: Javascript var foo = (a, b) => (a - 1) * (a - 1) - b; Verbose? Yes Tedious? Yes But please bear with me Let’s ‘fold’ the recursor function into the multiply function: JavaScript Javascript var doMultiply = (dm, a, b) => a ? b + dm(dm, a-1, b) : 0; var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))(doMultiply, a, b); JavaScript Lisp (defun do-multiply (dm a b) (if (zerop a) (+ b (funcall dm dm (1- a) b)))) (defun multiply (a b) (funcall #'do-multiply #'do-multiply a b)) And finally, let’s ‘fold’ the ‘doMultiply’ function into the multiply function Javascript var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))((dm, a, b) => a ? b + multiply(dm, a-1, b) : 0, a, b); JavaScript Lisp (defun multiply (a b) ((lambda (fxn a b) (funcall fxn fxn a b)) #'(lambda (dm a b) (if (zerop a) (+ b (funcall dm dm (1- a) b)))) a b)) Down the Rabbit Hole And just to be a little crazy, we’re not even gonna add automatically We’ll go through the process somewhat quicker here We start with a recursive function: Javascript var add(c, d) { return c ? + add(c - 1, d) : d; } Lisp (defun add (c d) (if (zerop c) d (1+ (add (1- c) d)))) JavaScript Redefine it to use the ‘recursor’ function (seen above): Javascript var doAdd = (da, c, d) => c ? + da(da, c-1, d) : d; var add = (c, d) => recursor(doAdd, c, d); var recursor = (fxn, a, b) => fxn(fxn, a, b); JavaScript Lisp 10 (defun do-add (da c d) (if (zerop c) d (1+ (funcall da da (1- c) d)))) (defun recursor (fxn a b) (funcall fxn fxn a b)) (defun add (c d) (recursor #'do-add c d)) So let’s look back at our ‘multiply’ function, and put in the ‘add’ function without folding it in: Javascript var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))((dm, a, b) => a ? add(b, multiply(dm, a-1, b)) : 0, a, b); JavaScript Lisp (defun multiply (a) (lambda (b) ((lambda (f) (funcall (funcall (funcall f f) a) b)) #'(lambda (dm) (lambda (x) (lambda (y) (if (zerop x) (add y (funcall (funcall (funcall dm dm) (1- x)) y)))))))) And then fold the add function together in a similar fashion Javascript Lisp var add = (c, d) => ((fxn, c, d) => fxn(fxn, c, d)) ((da, c, d) => c ? + da(da, c-1, d) : d, c, d); JavaScript (defun add (c d) ((lambda (fxn c d) (funcall fxn fxn c d)) #'(lambda (da c d) (if (zerop c) d (1+ (funcall da da (1- c) d)))) c d)) When we fold in the ‘add’ function; when c := b, and d := dm(dm, a-1, b), the add(b, dm(dm, a-1, b)) becomes: Javascript ((fxn, c, d) => fxn(fxn, c, d))((da, c, d) => c ? + da(da, c-1, d) : d, b, dm(dm, a-1, b)) JavaScript Lisp ((lambda (fxn c d) (funcall fxn fxn c d)) #'(lambda (da c d) (if (zerop c) d (1+ (funcall da da (1- c) d)))) b (funcall dm dm (1- a) b)) And putting it together, we get: Javascript Lisp var multiply = (a, b) => ((fxn, a, b) => fxn(fxn, a, b))((dm, a, b) => a ? ((fxn, c, d) => fxn(fxn, c, d))((da, c, d) => c ? d : + da(da, c-1, d), b, dm(dm, a-1, b)) : 0, a, b); JavaScript 10 11 12 13 14 (defun multiply (a b) ((lambda (fxn a b) (funcall fxn fxn a b)) #'(lambda (dm a b) (if (zerop a) ((lambda (fxn c d) (funcall fxn fxn c d)) #'(lambda (da c d) (if (zerop c) d (1+ (funcall da da (1- c) d)))) b (funcall dm dm (1- a) b)))) a b)) Or, putting it more one-letter-per-concept-y (i.e., cryptically): Javascript 10 11 var multiply = (a, b) => ((f, a, b) => f(f, a, b))( (f, a, b) => a ? ((g, c, d) => g(g, c, d))( (g, c, d) => c == ? d : + g(g, c-1, d), b, f(f, a-1, b)) : 0, a, b) JavaScript Lisp 10 11 12 13 14 (defun multiply (a b) ((lambda (f a b) (funcall f f a b)) #'(lambda (f a b) (if (zerop a) ((lambda (g c d) (funcall g g c d)) #'(lambda (g c d) (if (zerop c) d (1+ (funcall g g (1- c) d)))) b (funcall f f (1- a) b)))) a b)) That’s a lot of layers! And now we something interesting Suppose we only allow monads (functions with one input, not an ordered set of inputs) We can this in the following way Suppose we start with this: Javascript function foo(a, b, c) { return * a - (b - 1) * (c - 2); } JavaScript Lisp (defun foo (a b c) (- (* a) (* (1- b) (- c 2)))) We could have this return a function that takes only a, and returns a function which takes in (b, c) and outputs the same value This may seem like a lateral step, but notice that we’re getting rid of commas Javascript function foo(a) { return function(b, c) { return * a - (b - 1) * (c - 2); } } JavaScript Lisp (defun foo (a) (lambda (b c) (- (* a) (* (1- b) (- c 2))))) One step further, we get: Javascript function foo(a) { return function(b) { return function(c) { return * a - (b - 1) * (c - 2); } } } Lisp (defun foo (a) (lambda (b) (lambda (c) (- (* a) (* (1- b) (- c 2)))))) “Of course, a call with the original ‘foo’ would be something like: Javascript JavaScript foo(3,4,6) JavaScript Lisp (foo 6) And get -3 With the latest foo, we have to oo: Javascript foo(3)(4)(6) JavaScript Lisp (funcall (funcall (foo 3) 4) 6) And still we get -3 Doing this with ( )=>….-style expressions, we get the following progression: Javascript var foo = (a, b, c) => * a - (b - 1) * (c - 2); var foo = (a) => (b, c) => * a - (b - 1) * (c - 2); var foo = (a) => (b) => (c) => * a - (b - 1) * (c - 2); JavaScript And it works just like the final foo! Try it yourself Using these principles (and, admittedly, a little bit of trial and error (I know what I mean; why should the computer have to?), I derived the following expression for ‘multiply’: Javascript Lisp var multiply = (a)=>(b)=> ((f)=>(a)=>(b)=> f(f)(a)(b))( (f) => (a) => (b) => a ? ((g) => (c) => (d) => g(g)(c)(d))( (g) => (c) => (d) => c == ? d : + g(g)(c-1)(d))(b)(f(f)(a-1)(b)) : 0)(a)(b) JavaScript 10 11 12 13 14 15 16 17 (defun multiply (a) (lambda (b) ((lambda (f) (funcall (funcall (funcall f f) a) b)) #'(lambda (dm) (lambda (x) (lambda (y) (if (zerop x) (funcall ((lambda (c) (lambda (d) (funcall (funcall ((lambda (fxn) (lambda (a) (lambda (b) (funcall (funcall (funcall fxn fxn) a) b)))) #'(lambda (da) (lambda (c) (lambda (d) (if (zerop c) d (1+ (funcall (funcall (funcall da da) (1- c)) d))))))) c) d))) y) (funcall (funcall (funcall dm dm) (1- x)) y))))))))) "Adding words back in, you can see that it’s still pretty cryptic Javascript 10 11 Lisp var multiply = (multiplier)=>(multiplicand)=> ((doMultiply)=>(multiplier)=>(multiplicand)=> doMultiply(doMultiply)(multiplier)(multiplicand))( (doMultiply) => (multiplier) => (multiplicand) => multiplier ? ((doAdd) => (adder) => (addend) => doAdd(doAdd)(adder)(addend))( (doAdd) => (adder) => (addend) => adder == ? addend : + doAdd(doAdd)(adder-1)(addend)) (multiplicand)(doMultiply(doMultiply)(multiplier-1)(multiplicand)) : 0)(multiplier)(multiplicand) JavaScript 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 (defun multiply (multiplier) (lambda (multiplicand) ((lambda (multiplier-recursor) (funcall (funcall (funcall multiplier-recursor multiplier-recursor) multiplier) multiplicand)) #'(lambda (do-multiply) (lambda (multiplier) (lambda (multiplicand) (if (zerop multiplier) (funcall ((lambda (adder) (lambda (addend) (funcall (funcall ((lambda (adder-recursor) (lambda (multiplier) (lambda (multiplicand) (funcall (funcall (funcall adder-recursor adder-recursor) multiplier) multiplicand)))) #'(lambda (do-add) (lambda (adder) (lambda (addend) (if (zerop adder) addend (1+ (funcall (funcall (funcall do-add do-add) (1- adder)) addend))))))) adder) addend))) multiplicand) (funcall (funcall (funcall do-multiply do-multiply) (1- multiplier)) multiplicand))))))))) Try it yourself multiply(2)(3) yields This is a slow algorithm; it only counts one at a time So while we’re writing toy programs, why not go full hog with the crypticality? We don’t even need names (except fluid names to refer to variables within the current context – no ‘external’ variables): Javascript Lisp JavaScript ((c)=>(a)=>(b)=>c(c)(a)(b))((c)=>(a)=>(b)=>a?((d)=>(e)=>(f)=>d(d)(e)(f))((d)=>(e)=>(f)=>e==0?f:1+d(d)(e-1) 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 (funcall ((lambda (multiplier) (lambda (multiplicand) ((lambda (multiplier-recursor) (funcall (funcall (funcall multiplier-recursor multiplier-recursor) multiplier) multiplicand)) #'(lambda (do-multiply) (lambda (multiplier) (lambda (multiplicand) (if (zerop multiplier) (funcall ((lambda (adder) (lambda (addend) (funcall (funcall ((lambda (adder-recursor) (lambda (multiplier) (lambda (multiplicand) (funcall (funcall (funcall adder-recursor adder-recursor) multiplier) multiplicand)))) #'(lambda (do-add) (lambda (adder) (lambda (addend) (if (zerop adder) addend (1+ (funcall (funcall (funcall do-add do-add) (1- adder)) addend))))))) adder) addend))) multiplicand) (funcall (funcall (funcall do-multiply do-multiply) (1- multiplier)) multiplicand))))))))) 2) 3) Behind the Scenes If you try this with large numbers like 35,543,512 and 578,512, you’ll get either a stack error after a second or two, or if you have an insane amount of RAM and a great javascript interpreter, you might just freeze your computer for a day or so on a fast processor This is because, at a base level, what you are doing when you multiply and is this: 10 11 12 13 14 15 16 17 18 mult(2, 3) add(3, mult(1, 3)) add(3, add(3, mult(0, 3))) add(3, add(3, 0)) add(3, 1+ add(2, 0)) add(3, 1+ (1+ add(1, 0))) add(3, 1+ (1+ (1+ add(0, 0)))) add(3, 1+ (1+ (1+ 0))) add(3, 1+ (1+ 1)) add(3, 1+ 2) add(3, 3) 1+ add(2, 3) 1+ (1+ add(1, 3)) 1+ (1+ (1+ add(0, 3))) 1+ (1+ (1+ 3)) 1+ (1+ 4) 1+ So to get six, our final answer, we basically had to count one-by-one You may be wondering why we’re allowing the 1+x operation (such as (1 + 3) -> 4) to take place without deconstruction The reason why is because there has to be some atomic operation that changes numbers In Peano arithmentic (where this tutorial took inspiration from), this is known as the successor function That is to say, there is a ‘0’ (by law), and a ’S(uccessor)‘ function (by law) The 'meaning’ of the successor function of x (S(x)) is, intuitively, ‘one plus x’, but the axioms never state this (because they are an attempt at symbolic purity) So ‘1’ is just shorthand for S(0), ‘2’ is just S(1) = S(S(0)), and something like ‘11’ is just our place-value shorthand for S(S(S(S(S(S(S(S(S(S(S(0))))))))))) (whew) Recursion: Every Change Unquestionably Recurrant; Self Instantiation Over Nothing So what have we learned here? We’ve learned that recursion is possible without referring (explicitly) back to the original function, but we’ve also learned a valuable lesson about assumptions Even with something that beginning students find challenging (such as recursion), there is a wealth of knowledge to be found by asking even the most basic of questions.” ... S(S(S(S(S(S(S(S(S(S(S(0))))))))))) (whew) Recursion: Every Change Unquestionably Recurrant; Self Instantiation Over Nothing So what have we learned here? We’ve learned that recursion is possible without... lesson about assumptions Even with something that beginning students find challenging (such as recursion) , there is a wealth of knowledge to be found by asking even the most basic of questions.”

Ngày đăng: 10/09/2022, 09:05