Plenty of good answers already, but I’ll add this to clarify:
> A declarative if statement doesn't make sense.
That’s exactly right (and was the point I was trying to make, though I can see how it isn’t necessarily clear, particularly with the pseudo code syntax I chose). But a declarative if expression definitely makes sense.
Let me rewrite the example as an s-expression:
(if condition
a
b)
In a lisp, this would produce a value which is either a or b depending on the value of condition. (It would typically be implemented as a macro so only a or b is evaluated, but that’s an implementation detail.)
But an if expression is not the only way to declare conditions. For instance you might have something like:
(first
(filter predicate?
[a b]))
This isn’t exactly semantically equivalent, but it’s hopefully clear that it’s not imperative.
Of course lisps are generally (though not always) closer to the functional paradigm than a more declarative paradigm like say logic programming. But if you look at say Prolog, you’ll find that it too uses conditions for definition (though again with somewhat different semantics).
- The non-selected possibility isn't evaluated/executed (i.e. the evaluation of the possibilities is delayed until after one has been selected)
This is very important for imperative languages, since executing both branches would have observable effects even if we only returned one result.
This is also true in functional programming languages, even pure ones, since it's needed to e.g. terminate recursive calls. For example, if we define factorial by branching on whether the argument is 0 (base case) or not (recursive case), then evaluating both branches before selecting one would cause an infinite recursion for all arguments.
In the case of your Lisp code, we couldn't implement a recursive function like factorial unless we delay the evaluation e.g. by using macros to do the selection, or by switching from Lisp's default evaluation order (call-by-value) to something non-eager like call-by-name or call-by-need.
The imperative if in programming is different from the logical if. But it isn't disconnected from logic.
In fact, it is closely related to the logical and: it is and with short-circuiting, enabling the left expression to control effects in the right.
In Lisp, the two-form if, namely (if x y), can be replaced by (and x y), because and has the right logic and result value, and also the short-circuiting semantics that y is not evaluated if x is false.
If we want the logical if, we can use the equivalent expression (or (not x) y), which has the same truth table:
x y | (if x y) | (or (not x) y)
------------+-----------+----------------
nil nil | nil | t
nil t | nil | t
t nil | nil | nil
t t | t | t
The if/and relationship of course shows up in other languages:
if (pointer != NULL) {
if (pointer->foo == 42)
do();
}
Sigh. Declarative condition expressions are not control flow, they’re part of the definition. They are part of “what” and you could not define anything meaningful without them. The mistake is treating:
if condition: a else: b
As equivalent to:
if condition: do a else: do b
They might seem very similar, but they’re wildly different if you actually have the constraint. There is an implicit “do” somewhere in any program that has IO, but having it mixed in with a declarative program makes no sense and can’t make sense.
In the SQL example, nothing happens if you reach the conditional and exit immediately. In the imperative example you have no way of knowing what happened without inspecting the resulting state, or the code that produced it.
Yup. And the reason not to default to a list of then-forms and a list of else-forms is partly æsthetic, but partly dealing with the common case, because:
(if (eq foo bar) (baz))
looks like a function call, not returning the value of baz (using your syntax).
And of course Lisp is often written in a semi-functional way, so PROGN is, while not rare or really even avoided, not the usual way of doing things, usually.
(when (> x )
(printf a)
(return b))
(cond
((> x 3) (printf a)
(return b))
((< x 2) (printf b)
(return a))
(t (return c)))
Nobody who works with Lisps thinks in terms of "do I need double parentheses". You just know that if takes two or three expressions, when takes an expression followed by zero or more forms, cond takes a sequence of zero or more clauses which consist of a test followed by a body of zero or more forms.
If you're designing an S-exp syntax for something else, it's probably best to keep the familiar things the same; don't make some different if and such.
The Lisp if maps naturally to the ?: ternary operator; cond, when and unless can compile to cascaded if/else if/else.
This makes sense only when you have to have an explicit return point. In languages that return the last expression, I find it much more palatable to represent your conditions as top-level if expression.
A conversion of the base example would be:
function () {
if () {
x
} else {
if () {
y
} else {
z
}
}
}
Granted it's also worth noting in such a language I rarely find myself using `if` and `else`. More likely I'm doing flow control based on `map` and some version of `getOrElse` (Scala's term for Option that allows you to return an existing value or alternate to a nonexistent value). These abstractions tend to compose better, and lead to more clarity as to what particular data item is leading to the value being one thing or another; e.g.:
If requestValue is defined, return it; otherwise, if sessionValue is defined, return it; otherwise, return defaultValue. While not all conditions are immediately expressed this way, many of them can be simplified to data flow decisions that can be expressed this way, and the exercise of reworking the solution to use this strategy clarifies the surrounding program as well.
You did say that only Lisp implements "if" as a function, but I thought I'd share that in Haskell "if" is syntactic sugar for a case expression. That is:
if x then y else z
is sugar for:
case x of
True -> y
False -> z
There have been suggestions to replace this sugaring with a proper "if" function, e.g.:
if :: Bool -> a -> a -> a
if True x _ = x
if False _ y = y
It's been a long time since I used common lisp, but I seem to remember that the convention was to use if in a conditional expression and when for something that conditionally produces a side effect?
I don't mean to be the next snarky Lisp guy in the room, but I don't see the difference between reading
(if (= a 3) ...)
and thinking "that's a special form and a predicate", and reading
if a == 3:
...
and thinking "that's a special form and a predicate". In either case there's "if". Writing "(f x) could be anything" is a straw man. As soon as you replace "f" with "if" the argument falls apart.
(No, you can't name a function "if" in Common Lisp. Go ahead, try it.)
It's been a while since I used Common Lisp but isn't it recommended to use "if" for conditional expressions and "when"/"unless" for conditional statements?
I almost forgot—the IF statement is an expression! You can do the following:
A ? IF B = 15 THEN 40 ELSE 99
and A will be 40 if B is 15, otherwise A will be 99. There aren't many languages I've used that have allowed this
I need to introduce this man to the wonderful world of Haskell, Erlang, and other Functional Programming Languages.
rel p(s, x)
if(s = ‘a’) //this is sugar for [(s=’a’ and x=0) or x=2]
x = 0
else
x = 2
Why does it transform to
[(s=’a’ and x=0) or x=2]
and not
[(s=’a’ and x=0) or (s!=’a’ and x=2)]
(or whatever "not equals" is in Cosmos)?
In any language I know, "else" means "do this when the guard was _not_ true", and not "nondeterministically maybe do this other thing instead, if you want". Did I simply misunderstand it? (note that I got lost at the "soft cut" part, but did I get it right that "choose ... else" is closer to what I'd expect "if .. else" to do?)
Given that accessibility to people used to imperative languages is a core goal, I'm not sure that the choice of making "else" mean "yeah whatever" is the best one. Maybe it's better to replace it by a different keyword entirely ("if.. or.." maybe). On a related note, just in case you're not familiar with it, I believe that maybe the "if.. fi" construct in Dijkstra's Guarded Command Language[0] is an easy way to get imperative-thinking people to embrace nondeterminism. It feels a lot like normal imperative "if", except there is no "else". If you want "else", you need to add another guard explicitly, with the negation of your first guard. This way, it's easy for people to grasp that the order of the guarded commands have no meaning. It also makes introducing nondeterminism very explicit.
Hard to say how to properly refactor out the if statements without knowing the problem domain. And I guess that's basically the point - using if statements means that you are writing imperative code rather than modelling the problem. You are specifying to the compiler how you want things done, rather than what it is that you want done.
> A declarative if statement doesn't make sense.
That’s exactly right (and was the point I was trying to make, though I can see how it isn’t necessarily clear, particularly with the pseudo code syntax I chose). But a declarative if expression definitely makes sense.
Let me rewrite the example as an s-expression:
In a lisp, this would produce a value which is either a or b depending on the value of condition. (It would typically be implemented as a macro so only a or b is evaluated, but that’s an implementation detail.)But an if expression is not the only way to declare conditions. For instance you might have something like:
This isn’t exactly semantically equivalent, but it’s hopefully clear that it’s not imperative.Of course lisps are generally (though not always) closer to the functional paradigm than a more declarative paradigm like say logic programming. But if you look at say Prolog, you’ll find that it too uses conditions for definition (though again with somewhat different semantics).
reply