((lambda (x) (x x)) (lambda (x) (x x)))

Wednesday, February 18, 2009

More Higher Order Python: CPS based pure I/O in Python

Continuing with my quest to achieve the purest possible I/O and state handling possible in Python, here's another experiment. This one takes a somewhat different approach than the last one, and is based on continuation passing style. The portions of the code actually written in this implementation of CPS end up looking very Lisp-ish due to the abundance of brackets, though the semantics are quite different. Anyway, on with the code...

#!/usr/bin/python

#######################################################################################################################
# Continuation passing based, functionally pure I/O in python!
# Author: Gregory Masseau.
#######################################################################################################################

# This is the main program we're going to run, stored as a thunk so we don't have to worry about what's defined. Skim
# it, and you might get some idea what we're going to do.

main = lambda:chain( (inject("Hi. Enter a string, I will repeat it twice in uppercase.\n\nEntry: ")) (prntc)(discard)
                   , (input) (mul(2))(uc) (interject((prntc("Result:"))(discard))) (prnt) (discard)
                   , (inject("\nBye.\n"))(prnt) (end("Success."))
                   )

# Now lets define all the functions we need to handle this sort of code.

#######################################################################################################################
# These functions are combinators which transform a function into a curried version of itself. This means a function that
# was previously called using syntax line 'func(x,y)' must now be called using the syntax 'func(x)(y)' to achieve the
# same effect. The advantage is that it can also be called like 'func(x)' to partially apply it, returning a function
# that recieves the remaining variables.
def curried_2args(f):             return lambda x:lambda y:                            f(x,y)
def curried_3args(f):             return lambda x:lambda y:lambda z:                   f(x,y,z)
def curried_4args(f):             return lambda x:lambda y:lambda z:lambda w:          f(x,y,z,w)
#----------------------------------------------------------------------------------------------------------------------
# These functions are combinators that modify a function (which should already be curried) to behave in a continuation
# passing style, taking the continuation as an additional argument and continuing with it (passing what would ordinarily
# be the return value) to it instaed of returning it as they ordinarily would -- remember, in this style of programming
# only the last function ever returns.
def continuationpasser_thunk(f):  return lambda c:                                     c(f())
def continuationpasser_1arg(f):   return lambda x:lambda c:                            c(f(x))
def continuationpasser_2args(f):  return lambda x:lambda y:lambda c:                   c(f(x)(y))
def continuationpasser_3args(f):  return lambda x:lambda y:lambda z:lambda c:          c(f(x)(y)(z))
def continuationpasser_4args(f):  return lambda x:lambda y:lambda z:lambda w:lambda c: c(f(x)(y)(z)(w))
#----------------------------------------------------------------------------------------------------------------------
# This turns a value into a value expecting a continuation.
def inject(x):                    return lambda c:                                     c(x)
# This evaluates it's argument and calls it's continuation without it.
def discard(x):                   return lambda c:                                     c
# This is basically shortand for (discard)(inject x).
def restart(x):                   return lambda y:lambda c:                            c(x)
# This is fun. This takes another sequence of commands in CPS format, and runs those, before passing it's argument to
# it's continuation.
def interject(f):                 return lambda x:lambda c:                            c(x) if f else c(x)
# This just ends with the exit status/message 'v', expecting no continuation.
def end(v):                       return lambda x:                                     (inject(v))(id)
# Since inject just passes it's argument to it's continuation, it can be used as a no op in CPS code blocks.
def noop(x):                      return                                               inject(x)
#----------------------------------------------------------------------------------------------------------------------
# This is kind of like (but not quite, I don't think) a monadic bind, taking a thunk and a function and returning a thunk that does the first function to the result of the thunk, returning the result of the second.
def chained(x,y):                 return lambda:                                       y(x())
# This is just the previous function extended to operate on n thunks, where 1 <>
def chain(*fs):                   return                                               reduce(chained,fs)
#----------------------------------------------------------------------------------------------------------------------
# The identity function. End a CPS function sequence with it to return a value into normal code.
def id(x):                        return                                               x
#######################################################################################################################
# These are some trivial math and I/O functions, appropriately curried and lifted to work in our CPS model.
# Adds two objects implementing +.
@continuationpasser_2args
@curried_2args
def add(x,y):                     return                                               y+x
#----------------------------------------------------------------------------------------------------------------------
# Multiplies two objects implementing *.
@continuationpasser_2args
@curried_2args
def mul(x,y):                     return                                               y*x
#----------------------------------------------------------------------------------------------------------------------
# Increments a number.
@continuationpasser_1arg
def inc(x):                       return                                               1+x
#----------------------------------------------------------------------------------------------------------------------
# Takes input from stdin.
@continuationpasser_thunk
def input():                     return                                                raw_input()
#----------------------------------------------------------------------------------------------------------------------
@continuationpasser_1arg
# Prints a value to stdout.
def prnt(s):
                                 print                                                 str(s)
                                 return                                                True
#----------------------------------------------------------------------------------------------------------------------
# Prints a value to stdout, printing no newline after.
@continuationpasser_1arg
def prntc(s):
                                 print                                                 str(s),
                                 return                                                True
#----------------------------------------------------------------------------------------------------------------------
# Returns a string in uppercase.
@continuationpasser_1arg
def uc(s):                       return                                                s.upper()
#######################################################################################################################

# Now we call main, executing our program.
main()

# Instead of calling main as above, just uncommenting this will also have a similar effect, though it's a little less
# pure since it is techincally three statements when used that way.
#(inject("Hi. Enter a string, I will repeat it twice in uppercase.\n\nEntry: "))(prntc)(discard)
#(input)(mul(2))(uc)(interject((prntc("Result:"))(discard)))(prnt)(discard)
#(inject("\nBye.\n"))(prnt)(end("Success."))

# The following is equally valid, if somewhat less readable, as well as about as pure as this gets right now.
#(inject("Hi. Enter a string, I will repeat it twice in uppercase.\n\nEntry:"))(prntc)(discard)(input)(mul(2))(uc)(interject((prntc("Result:"))(discard)))(prnt)(discard) (inject("\nBye.\n"))(prnt)(end("Success."))

Monday, February 16, 2009

Higher Order Python

Just for fun, here's an example of how one can completely ignore the bias towards a statement oriented programming style that's usually prevalent in Python and handle use I/O in a purely functional manner.

def appliedto(x): return lambda y:y(x())
def adder(x):     return lambda y:int(x)+int(y)
def l_msg(s):     return lambda:prnt(s)
def l_msgc(s):    return lambda:prntc(s)
def l_v(v):       return lambda:v
def ife(p,t,e):   return lambda x: t() if p(x) else e()
def input():      return raw_input()
def seqe(*a):     return reduce(binde,a)
def seq(*a):      return reduce(bind,a)
def binde(x,y):   return lambda:y(x())
 
def bind(x,y):
  def actionpair(*a):
    x(*a)
    return y()
  return actionpair

def passahead(x,y):
  def actionpair(*a):
    x()
    return y(*a)
  return actionpair

def prnt(s):
  print s
  return True

def prntc(s):
  print s,
  return True

hello     = l_msg("Hello!")
goodbye   = l_msg("Goodbye.")
question1 = l_msgc("Enter the first addend: ")
question2 = l_msgc("Enter the second addend:")
desc      = l_msg("Enter two numbers, and I will add them.")
smsg      = l_msgc("The sum of the numbers is ")
printnl   = l_msg("")

seq(hello
   ,printnl
   ,desc
   ,printnl
   ,question1
   ,seqe(input
        ,adder
        ,appliedto(seq(question2
                      ,input))
   ,passahead(seq(printnl
                 ,smsg)
             ,prnt))
   ,printnl
   ,goodbye
   ,printnl)()

Friday, February 13, 2009

Func Users Module version 0.5 is released.

Release 0.5, released today, consists a final polishing of the Users & Groups module before it's initial release.

Changes in this version included:

- register_method_args has been implemented in users.py
- unit tests for the methods in user.py have been added to test_client.py

As of this release, all features intended for the release version of this module are present and working, and it appears to be ready for merging.

The code is linked off the project page here.