### On writing Python one-liners.

# I'm off to the Oregon Shakespeare Festival for the weekend so no

# time to finish my Untangling series. But I do have time to post this...

# The other day someone asked me if it was possible to implement a

# certain task in one line of Python. My response was "yes, but you

# wouldn't want to." You can think of what follows as the demonstration

# that you really don't want to.

# The problem is, there are lots of Python constructions you can't

# do on one line: assigning variables, defining functions, using

# conditionals, recursion and so on. So it appears there's a limit

# to what you can do. Amazingly, all of these problems can be solved

# with the use of lambdas.

# One place you often want one line lambdas is in the use of the map

# function. So let's stick with that as an example and warm up with

# an easy example. Lets say we want to add 1 to all of the numbers

# from 0 to 19. We can write:

def inc(n):

return n+1

print "1.",map(inc,range(0,20))

# Lambdas enable you to write that as a one-liner:

print "1.",map(lambda x:x+1,range(0,20))

# Now what happens if we want to apply a function that uses a local

# (constant) variable. For example:

def f1(n):

m = 2*n+1

return m+m*m

print "2.",map(f1,range(0,20))

# We can eliminate the assigment by means of a function. m changes from

# being a local to a function argument:

def f2(n):

def g(m):

return m+m*m

return g(2*n+1)

print "2.",map(f2,range(0,20))

# But that seems to make things worse because now we have a function

# definition instead of a variable assigment. But we can eliminate

# that with a lambda:

def f3(n):

return (lambda m:m+m*m)(2*n+1)

print "2.",map(f3,range(0,20))

# And now we can write the whole thing as one big lambda:

print "2.",map(lambda n:(lambda m:m+m*m)(2*n+1),range(0,20))

# But what happens if we want to define a bunch of local functions

# inside our lambda? We can use the same technique. We (1) use

# lambdas to allow us to fake local variable definitions and

# (2) use lambdas to define the functions. So we can replace this:

def f(n):

def g(n):

return 2*n+1

def h(n):

return 3*n-2

return g(h(g(h(n))))

print "3.",map(f,range(0,20))

# with this:

print "3.",map(lambda n:(lambda g,h,n:g(h(g(h(n)))))(lambda n:2*n+1,lambda n:3*n-2,n),range(0,20))

# We're passing in lambdas as arguments to a lambda so that they

# get bound to g and h.

# But what happens if we want a recursive definition like:

def fact(n):

if n<=1:

return 1

else:

return n*fact(n-1)

print "4.",map(fact,range(0,20))

# Because the function is bound to the name fact, we can refer to

# fact by name inside the defintion of fact. How can we do this in

# a one-line lambda? We can start by eliminating the self-reference

# in a fairly standard way. First rewrite fact so it doesn't call

# itself, but calls a function passed in as an argument:

def urfact(f,n):

if n<=1:

return 1

else:

return n*f(f,n-1)

# We can then make a factorial function by calling urfact with itself,

# allowing it to carry out the recursion:

def fact1(n):

return urfact(urfact,n)

print "4.",map(fact1,range(0,20))

# We can use urfact directly without fact1:

print "4.",map(lambda n:urfact(urfact,n),range(0,20))

# Now all we need is a one-liner definition of urfact. It's tempting to try:

def urfact1(f,n):

return {True:1,False:n*f(f,n-1)}[n<=1]

# But that results in non-termination because it evaluates *both* branches

# of the conditional. In a lazy language like Haskell, the branch not taken

# wouldn't be evaluated. But Python can be made lazy by using...you

# guessed it...lambda. The expression lambda:x is like a lazy version

# of the expression x. It doesn't get evaluated until you apply it as a

# function of no arguments, at which time it takes on the value x. So we

# can rewrite urfact as:

def urfact2(f,n):

return {True:lambda:1,False:lambda:n*f(f,n-1)}[n<=1]()

# And we get

print "4.",map(lambda n:urfact2(urfact2,n),range(0,20))

# Note how we use urfact2 twice, so we need the trick above for local

# definitions to get it down to one reference:

print "4.",map(lambda n:(lambda f:f(f,n))(urfact2),range(0,20))

# And now we can replace urfact2 with a lambda

print "4.",map(lambda n:(lambda f:f(f,n))(lambda f,n:{True:lambda:1,False:lambda:n*f(f,n-1)}[n<=1]()),range(0,20))

# And that's enough for now. I expect that if we keep going we'll find

# way to rewrite any Python program as a one liner. You could probably

# even write a program to automatically 'compile' a usable subset of

# Python into one liners.

# But you probably don't want to do that.