#!/bin/perl # Perl meets lambda calculus # Chuck Liang, for CSC123/252, Hofstra University Computer Science, # If you're an experienced programmer, learning enough about perl to # understand this document should be a day or two's work, maybe less. # I've some tutorial links to help you. Focus on how functions # (subroutines) are defined in perl. # To run this program, chmod +x filename, or type perl filename. # S K and I combinators: sub I # lambda x.x { $_[0]; } # $_[0] is the first argument to subroutine I # one might be tempted to define K as simply sub{$_[0]} also, but # that's not correct since it really should return a lambda term if only # applied to one argument: sub K # lambda x. lambda y. x { my $x = $_[0]; # x records the first argument sub { $x; } # inner lambda term is what's returned } sub S # lambda x.lambda y.lambda z.(x z) (y z) { my $x = $_[0]; sub { my $y = $_[0]; sub { my $z = $_[0]; $x->($z)->($y->($z)); #&{&$x($z)}(&$y($z)); } } } # testing to see if things works my $KI = K \&I; # same as &K(\&I); # \&I is a pointer to function I - unfortunatet that this is not automated print (I whatever); print "\n"; # same as &I(whatever) print &K(3)->(4), "\n"; # same as : print &{K 3}(4) , "\n"; # prints 3 print $KI->(3)->(4), "\n"; # print &{&$KI(3)}(4), "\n"; # prints 4 my $SKI = S(\&K)->(\&I); # same as &{&S(\&K)}(\&I) # SKI should reduce to I, so let's see if it does: print $SKI->(3), " ", $SKI->(ok), "\n"; print "Now for booleans:\n"; # Lambda booleans: # here, unlike K and $KI, I will cheat and assume that the # true and false combinators are always applied to 2 arguments. # this will allow me to write them more easily in perl, without # having to write functions that return functions. # Similarly, I will assume that IFELSE is always applied to 3 args. sub T { $_[0]; } sub F { $_[1]; } sub IFELSE0 { my $bool = $_[0]; &$bool($_[1],$_[2]); } # unfortunately, IFELSE0 above won't work because perl is call-by-value! # it'll evaluate the second argument $_[2] anyway (too bad!). # However, we can fake call-by-name by making sure that # IFELSE always gets a pair of subroutines. These subroutines # are both vacuous. That is, instead of 3, we use sub{3}! # then, to extract the value, we apply the sub returned by IFELSE0 # to a dummy argument: sub IFELSE { IFELSE0(@_)->(dummy) } print &IFELSE(\&T, sub{3}, sub{4}), "\n"; # prints 3 print &IFELSE(\&F, sub{3}, sub{4}), "\n"; # prints 4 # let's see if the call-by-name semantics of ifelse is captured: my $x = 3; #print &IFELSE0(\&T, "ok", 1/($x-3)), "\n"; # will crash print IFELSE(\&T, sub{"ok"}, sub{1/($x-3)}), "\n"; # will be "ok"! # This scheme works because most higher-order languages uses # something called "weak beta reduction" - which means they # will not normalize the body of a lambda term until the # term is applied. # P.S. don't confuse call-by-name with call-by-reference. In # some ways call-by-reference is more similar to call-by-name than # call-by-value, but they are not the same, despite assertions by # some careless people. If you pass a perl function a variable by # itself - as in &f(x), then x is passed by reference. Weird huh? # In order to simulate call-by-name, you must pass a pointer to CODE, # not value - that's why we passed subroutines to IFESLE! #------- # lambda data structures: the word "cons" is borrowed from Lisp/Scheme. # I will use TRUE to select the first (head) item of the structure, and # FALSE to select the second (tail) item of the structure. # The idea here is that cons returns a function encapsulating # two values (head and tail). The function takes a selector # (true or false, 1 or 2 doesn't matter) and returns the value # asked of it. The function serves as an interface between the # internal data and outside access - similar to the principal of # object-oriented programming. However, to have true objects, # we'll have to wait a bit longer. Something new entirely is needed! sub CONS { my $head = $_[0]; my $tail = $_[1]; sub { &IFELSE($_[0], sub{$head}, sub{$tail}); } } # functions to extract items from CONS structure (traditional Lisp names) # car extracts the head: sub CAR { my $struct = $_[0]; $struct->(\&T); } # cdr extracts the tail: sub CDR { my $struct = $_[0]; $struct->(\&F); } my $primes = CONS (2,CONS (3,CONS (5,CONS (7,CONS (11,thatsenough))))); # same as &CONS(2,&CONS(3,... print "and the third prime number is: ", CAR (CDR (CDR $primes)), "!\n"; # Perl's built-in recursion. sub factorial { my $n = $_[0]; if ($n<2) {1;} else {$n * &factorial($n-1);} } # print &factorial(6), "\n"; # Applicative order Y-combinator # lambda M. (lambda x. M (lambda y. (x x y))) # (lambda x. M (lambda y. (x x y))) sub Y { my $M = $_[0]; my $N = sub { my $x = $_[0]; $M->(sub{ $x->($x)->($_[0]) }); }; $N->($N); } # new n-factorial my $fact = Y sub{ my $f= $_[0]; sub { my $n = $_[0]; if ($n<2) {1;} else {$n * $f->($n-1);} } }; print "6! is ", $fact->(6), "\n"; print "---------------------\n\n"; # ------------------------ # EXTRA CREDIT for JAVA programmers: *** # in java you can inline a class that implements a generic interface, # as in # new WindowAdapter() { # public void windowClosing(WindowEvent e) {System.exit(0);} } # So in some ways it is possible to write a java function (method) # that returns a function. How far can you take this idea? Would # it be possible to use Java as we used Perl above? Can you write # S, K and I? IFELSE? If not, what is it about Java that's stopping # you from doing it. # There's no due date on the extra credit - think about it now and # then during the semester.