buggyCode x y = if foo x
then bar x y
else baz y
I can wrap anything on the right-hand-side in a printf (except the keywords if/then/else). At the extreme end I could do: buggyCode x y = trace "hit buggy code" (if trace "applying foo to x" ((trace "hit foo" foo) (trace "hit foo's x" x)
then trace "applying bar x to y" ((trace "applying bar to x" ((trace "hit bar" bar) (trace "hit bar's x" x))) (trace "hit bar's y" y))
else trace "applying baz to y" ((trace "hit baz" baz) (trace "hit baz's y" y))
I could even move the x and y arguments across to the right-hand-side using anonymous function notation, then I can trace partial applications too: buggyCode = trace "hit buggyCode" (\x -> trace "gave x to buggyCode" (\y -> trace "gave y to buggyCode" <previous right-hand-side goes here>))
Note that we the built-in if/then/else is mostly a legacy crutch to aid familiarity. We can just use a function instead, and printf all the things: ifThenElse True x y = x
ifThenElse False x y = y
buggyCode = \x -> (\y -> (ifThenElse (foo x)
(bar x y)
(baz y)))
(Of course, defining our own if/then/else isn't much use; we're usually better off writing more meaningful, domain-specific alternatives)As for that `ifThenElse` example, there's nothing "meta" going on at all and there's no fancy control flow: it's just a (very simple) function. Consider a function like:
renderPage User page register = page
renderPage Guest page register = register
Doesn't look like 'clever spaghetti' to me: we branch between 'User' and 'Guest', returning a different one of our arguments for each. Yet this is identical to that 'ifThenElse' function, except for the names (which are irrelevant to the machine).This is what I meant by writing "more meaningful, domain-specific alternatives". Not to "re-invent control flow", but to avoid talking about control flow, and just use functions.
Note that we can do pretty much the same in any language, e.g. Python:
def ifThenElse(c, x, y):
return {True: x, False: y}[c]
def renderPage(session, page, register):
return {User: page, Guest: register}[session]
Nothing "meta" or "re-inventing the flow-control wheel" there; just some functions. Of course, this isn't a great idea in Python since Python is impure and uses eager evaluation.Note that such "meta" "reinvented control structures" appear all over the place in OOP, due to dynamic dispatch, e.g.
class User:
def renderPage(page, register):
return page
class Guest:
def renderPage(page, register):
return register
These are the two solutions to the "expression problem":- FP restricts the allowed values but allows new functions to use them. For example, adding an Admin session would be hard (need to rewrite renderPage), but adding a renderAvatar function is easy.
- OOP restricts the allowed functions (methods), but allows them to use new values. For example, adding an Admin class would be easy, but adding a renderAvatar method would be hard (need to rewrite User and Guest).
Scheme isn't, lambda is. That paper is about how lexical closures alone are enough to simulate a lot of imperative concepts like assignment, loops, and gotos (they do cheat and use set! in a couple places, but the point of the paper is how much lambda application can do all on its own). Your broader point about Lisps not being particularly functionally oriented is correct, though; they're some of the most unopinionated languages you could imagine.