Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Tuesday, March 2, 2021

Roll your own conditional

Sometimes you'd like to write your own conditionals. Perhaps you'd like to wrap something around one or both arms of the branch. Perhaps the predicate is tristate and you need three branches. Perhaps the predicate wants to share information with one or both arms. You want to abstract away how the predicate makes it decision from how the caller wants to handle the possible outcomes. The trick is to use continuation passing style:

sign = lambda n, if_positive, if_zero, if_negative: \
           if_positive() if n > 0 else \
           if_negative() if n < 0 else \
           if_zero()

# use it like this:
for i in range (-1, 1):
    print (i)
    print (sign (i,
            lambda: \
              "positive",
            lambda: (lambda first, second: second)(
              print ("Hooray!"),
              "zero"),
            lambda: \
              "negative"))
By making the branches be thunks, we delay evaluation. Once the conditional decides which branch to take, it forces the thunk and returns its value.

Monday, March 1, 2021

Some of Python's language constructs, like variable binding, exception handling, sequencing, and iteration, are provided only through language statements. You cannot use statements in an expression context, so it would seem we are up against a limitation. But Python has lambda expressions. Lambda expressions and function calls are all you need to implement variable binding, sequencing and recursion as ordinary Python expressions.

For example, suppose you wish to have a 'let' expression where you bind helper variables 'a' and 'b' to subexpressions. Simply use lambda and immediately call it:

    return (lambda a, b: h(a, b))(f(x), g(y))
Not pretty, but it gets the job done.

If you want to sequence two expressions, write them like this:
    return (lambda ignore: x + 7)(print (x))
The print expression runs first, its result is ignored and then x is added to 7 and returned. Again, this could use a lot of syntactic sugar, but it gets the job done. Alternatively, we could use left to right evaluation of arguments to do our sequencing:
    return (lambda first, second: second)(print(x), x + 7)
In order to implement iteration, we need to bind a name recursively. We'll use the Y operator.
Y = lambda f: (lambda d: d(d)) (lambda x: f (lambda: x (x)))

print (Y (lambda loop:  \
              lambda x: \
                  None if x == 0 else (lambda first, second: second)(
                                           print (x),
                                           loop()(x - 1)))
       (5))

5
4
3
2
1
0
None

Handling exceptions is easy if you use a helper function and some thunks:

def on_error (compute_value, handle_error):
    try:
        return compute_value()
    except:
        return handle_error()


def safe_divide (dividend, divisor):
    return on_error (
           lambda: dividend / divisor,
           lambda: (lambda first, second: second)(
                       print ('divide by zero, answer 1'),
                       1))

print (safe_divide (12, 3))
4
print (safe_divide (12, 0))
divide by zero, answer 1
1