Patrick (patrickwonders) wrote,

  • Mood:

Looking for the right hammer...

I am working on some CFFI bindings for OpenMPI. Most of the MPI functions return their results with OUT parameters whilst the function itself returns an :int status code. As such, I find myself doing this sort of thing a bunch:

(let ((val (some-function-call with some args)))
     (if (zerop val)
         (do-another-thing-with val)))

Being a big DRY fan, I am hoping to refactor this a bit. So, I'm looking for the right phrasing.

There are two sorts of standard Lisp paradigms that come to mind from the above pattern—the anaphoric if and the binding if. In C/C++, there are (at least) two different things which are false (integer zero and the null pointer) while everything else is true. In Java, there is only one thing that is false (Boolean false) and only one thing that is true (Boolean true). In Perl, there are infinitely many things that are false (undefined, the empty string, the empty array, the empty hash table, the integer zero, and any string whose leading numeric portion is zero (except the string '0 but true')) and everything else is true. In Lisp, there is only one false (NIL, which is also the empty list) and everything else is true.

In C (or Perl), one often takes advantage of the multiplicity of trues (or falses) in some sort of situation like this:

/* C version */
const char* error = do_something( in1, in2 );
if ( error ) {
    fprintf( stderr, "Error: %s\n", error );

# Perl version
if ( my $error = do_something( $in1, $in2 ) ) {
    print STDERR "Error: $error\n";

The out-of-the-box Common Lisp method looks more like the C version above:

(let ((error (do_something in1 in2)))
    (when (error)
          (format *error-output* "Error: ~A~%" error)))

But, rather than have to do the whole (let ...) dance every time, one can use Lisp macros to define an anaphoric if or an anaphoric when. An anaphor is a word or phrase that refers to an earlier word or phrase. The typical anaphoric when would be expressed like this:

(awhen (do_something in1 in2)
    (format *error-output* "Error: ~A~%" it))

Which, if you expanded the macro, would look like this:

(let ((it (do_something in1 in2)))
     (when (it)
           (format *error-output* "Error: ~A~%" it)))

The binding if and binding when give you more control by letting you name the variable instead of assuming that you're fine just calling the variable it.

(bwhen (error (do_something in1 in2))
       (format *error-output* "Error: ~A~%" error))

The binding if doesn't really work for my pattern though. Because zero is true in Lisp, I cannot just do:

(bif (val (some-function-call with some args))
     (do-another-thing-with val)

So, I need to bind a value to some variable, then run some predicate (a function that returns true or false) on the bound result. I don't want to bind the result of the predicate. I want to bind the thing I am passing to the predicate.

Here are some different ways that I'm kicking around making this go. (Note: the #'zerop is a predicate that returns NIL except when its argument is zero.)

;; [v1]: simple, straight forward, yada, yada
(aif-test #'zerop (some-function-call with some args)
          (do-another-thing-with it))

;; [v2]: but, since I don't like the implied variable name
(bif-test #'zerop (val (some-function-call with some args))
          (do-another-thing-with val))

;; [v3]: a little uglier, but somewhat more in line with other binding things
(bif-test (zerop (val (some-function-call with some args)))
          (do-another-thing-with val))

;; [v4]: but the uglier version could even let me bind multiple things
;; (with the obvious expense of needing to bind every argument)
(bif-test (< (v1 (some-func)) (v2 (some-other-func)))
          (do-one-thing-with v1 v2)
          (do-another-thing-with v1 v2))

;; but, then I'd want to be able to recurse into things, too, and it
;; all comes tumbling down:
(bif-test (plusp (s1 (+ (v1 (some-func)) (v2 (some-other-func)))))
          (do-one-thing-with v1 v2)
          (do-another-thing-with s1))

And, I'm just not going to get out of that alive. I could try to recurse, but I wouldn't know to stop recursing at (some-func ...) if it has args that were other function calls. At least not without playing massive introspection games... and even then only if I am sure that the variables I want to bind to values aren't elsewhere bound to functions.

Warning: lots of semi-thought-out rambling here. I am mostly just typing as I work through contingencies. This is not finished text. It is barely above stream-of-consciousness.

A different way out of this that I may explore further is to try to flag bindings. So, rather than just assume that I will bind every argument and every return result in my expression, I would flag the spots to bind, something like:

(fb-if (zerop +bind+ val (some-function-call with some args))
       (do-another-thing-with val))

But, that would still have problems with recursion, I suppose, if someone wanted to do something like this:

(fb-if (not +bind+ val (and (should-get-num *stream*)
                            (fb-if (plusp +bind+ num (get-num *stream*))
                                   (- num))))
       (something-with-some-true-val val)

If I just recursively sought out the +bind+ markers on the expression, then I would end up calling (get-num) to bind it to num all of the time even though the (and ...) semantics should prevent (get-num) from being called if (should-get-num) returned NIL. So, I definitely shouldn't be recursively looking through trying to bind things.

Of course, thinking about the semantics of (and ...), I am realizing a flaw in the idea of using an n-argument predicate and binding all of its arguments like in [v4] above. At the time I would have to bind them, I would not yet know if they should be evaluated at all.

(bwhen-test (and (val1 (get-a-thing)) (val2 (get-a-thing)))
            (do-something-with val1 val2))

I suppose I coudl expand that into something like this:

(let (val1 val2)
     (when (and (setf val1 (get-a-thing)) (setf val2 (get-a-thing)))
           (do-something-with val1 val2)))

This would still be troublesome in the recursive, flagged scenario if one of the subexpressions bound to the same variable name as an outer expression. I can some think of some hacks around that (most of which would require hours with (macroexpand-1 ...) to nail down), but anyway.

For now, I'm just going to stick with single-argument predicates especially since for my current code, I will probably only need #'zerop as zero is false in C but not in Lisp. In fact, I may just start with:

(bif-zerop (val (some-function-call with some args))
           (do-another-thing-with val))
Tags: lisp
  • Post a new comment


    default userpic

    Your reply will be screened