Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does `if` not evaluate all its arguments?

Tags:

lisp

I'm trying to learn and understand the Lisp programming language to a deep level. The function + evaluates its arguments in applicative order:

(+ 1 (+ 1 2))

(+ 1 2) will be evaluated and then (+ 1 3) will be evaluated, but the if function works differently:

(if (> 1 2) (not-defined 1 2) 1)

As the form (not-defined 1 2) isn't evaluated, the program doesn't break.

How can the same syntax lead to different argument evaluation? How is the if function defined so that its arguments aren't evaluated?

like image 419
eliocs Avatar asked Sep 29 '13 10:09

eliocs


People also ask

How if condition is evaluated?

The conditions are evaluated to a Boolean value, which should be either TRUE or FALSE. If the condition is found to be TRUE, the corresponding code will be executed, and there will be no other conditions to be evaluated.

Are IF statements evaluated left to right?

It will evaluate from left to right and short-circuit the evaluation if it can (e.g. if a evaluates to false it won't evaluate b). If you care about the order they are evaluated in you just need to specify them in the desired order of evaluation in your if statement.

How is or condition evaluated in C++?

The logical OR operator ( || ) returns the boolean value true if either or both operands is true and returns false otherwise. The operands are implicitly converted to type bool before evaluation, and the result is of type bool .


2 Answers

if is a special operator, not an ordinary function.

This means that the normal rule that the rest elements in the compound form are evaluated before the function associated with the first element is invoked is not applicable (in that it is similar to macro forms).

The way this is implemented in a compiler and/or an interpreter is that one looks at the compound form and decides what to do with it based on its first element:

  • if it is a special operator, it does its special thing;
  • if it is a macro, its macro-function gets the whole form;
  • otherwise it is treated as a function - even if no function is defined.

Note that some special forms can be defined as macros expanding to other special forms, but some special forms must actually be present.

E.g., one can define if in terms of cond:

(defmacro my-if (condition yes no) 
  `(cond (,condition ,yes)
         (t ,no)))

and vice versa (much more complicated - actually, cond is a macro, usually expanding into a sequence of ifs).

PS. Note that the distinction between system-supplied macros and special operators, while technically crisp and clear (see special-operator-p and macro-function), is ideologically blurred because

An implementation is free to implement a Common Lisp special operator as a macro. An implementation is free to implement any macro operator as a special operator, but only if an equivalent definition of the macro is also provided.

like image 142
sds Avatar answered Oct 23 '22 05:10

sds


sds's answer answers this question well, but there are a few more general aspects that I think are worth mentioning. As that answer and others have pointed out, if, is built into the language as a special operator, because it really is a kind of primitive. Most importantly, if is not a function.

That said, the functionality of if can be achieved using just functions and normal function calling where all the arguments are evaluated. Thus, conditionals can be implemented in the lambda calculus, on which languages in the family are somewhat based, but which doesn't have a conditional operator.

In the lambda calculus, one can define true and false as functions of two arguments. The arguments are presumed to be functions, and true calls the first of its arguments, and false calls the second. (This is a slight variation of Church booleans which simply return their first or second argument.)

 true = λ[x y].(x)
false = λ[x y].(y)

(This is obviously a departure from boolean values in Common Lisp, where nil is false and anything else is true.) The benefit of this, though, is that we can use a boolean value to call one of two functions, depending on whether the boolean is true or false. Consider the Common Lisp form:

(if some-condition
  then-part
  else-part)

If were were using the booleans as defined above, then evaluating some-condition will produce either true or false, and if we were to call that result with the arguments

(lambda () then-part)
(lambda () else-part)

then only one of those would be called, so only one of then-part and else-part would actually be evaluated. In general, wrapping some forms up in a lambda is a good way to be able delay the evaluation of those forms.

The power of the Common Lisp macro system means that we could actually define an if macro using the types of booleans described above:

(defconstant true
  (lambda (x y)
    (declare (ignore y))
    (funcall x)))

(defconstant false
  (lambda (x y)
    (declare (ignore x))
    (funcall y)))

(defmacro new-if (test then &optional else)
  `(funcall ,test
            (lambda () ,then)
            (lambda () ,else)))

With these definitions, some code like this:

(new-if (member 'a '(1 2 3))
  (print "it's a member")
  (print "it's not a member"))))

expands to this:

(FUNCALL (MEMBER 'A '(1 2 3))                     ; assuming MEMBER were rewritten 
         (LAMBDA () (PRINT "it's a member"))      ; to return `true` or `false`
         (LAMBDA () (PRINT "it's not a member")))

In general, if there is some form and some of the arguments aren't getting evaluated, then the (car of the) form is either a Common Lisp special operator or a macro. If you need to write a function where the arguments will be evaluated, but you want some forms not to be evaluated, you can wrap them up in lambda expressions and have your function call those anonymous functions conditionally.

This is a possible way to implement if, if you didn't already have it in the language. Of course, modern computer hardware isn't based on a lambda calculus interpreter, but rather on CPUs that have test and jump instructions, so it's more efficient for the language to provide if a primitive and to compile down to the appropriate machine instructions.

like image 28
Joshua Taylor Avatar answered Oct 23 '22 06:10

Joshua Taylor