Reputation: 10070
Sometimes, I have a control structure (if, for, ...), and depending on a condition I either want to use the control structure, or only execute the body. As a simple example, I can do the following in C, but it's pretty ugly:
#ifdef APPLY_FILTER
if (filter()) {
#endif
// do something
#ifdef APPLY_FILTER
}
#endif
Also it doesn't work if I only know apply_filter at runtime. Of course, in this case I can just change the code to:
if (apply_filter && filter())
but that doesn't work in the general case of arbitrary control structures. (I don't have a nice example at hand, but recently I had some code that would have benefited a lot from a feature like this.)
Is there any langugage where I can apply conditions to control structures, i.e. have higher-order conditionals? In pseudocode, the above example would be:
<if apply_filter>
if (filter()) {
// ...
}
Or a more complicated example, if a varable is set wrap code in a function and start it as a thread:
<if (run_on_thread)>
void thread() {
<endif>
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
sleep(1);
}
<if (run_on_thread)>
}
start_thread(&thread);
<endif>
(Actually, in this example I could imagine it would even be useful to give the meta condition a name, to ensure that the top and bottom s are in sync.)
I could imagine something like this is a feature in LISP, right?
Upvotes: 1
Views: 113
Reputation: 36622
Any language with first-class functions can pull this off. In fact, your use of "higher-order" is telling; the necessary abstraction will indeed be a higher-order function. The idea is to write a function applyIf
which takes a boolean (enabled/disabled), a control-flow operator (really, just a function), and a block of code (any value in the domain of the function); then, if the boolean is true, the operator/function is applied to the block/value, and otherwise the block/value is just run/returned. This will be a lot clearer in code.
In Haskell, for instance, this pattern would be, without an explicit applyIf
, written as:
example1 = (if applyFilter then when someFilter else id) body
example2 = (if runOnThread then (void . forkIO) else id) . forM_ [1..10] $ \i ->
print i >> threadDelay 1000000 -- threadDelay takes microseconds
Here, id
is just the identity function \x -> x
; it always returns its argument. Thus, (if cond then f else id) x
is the same as f x
if cond == True
, and is the same as id x
otherwise; and of course, id x
is the same as x
.
Then you could factor this pattern out into our applyIf
combinator:
applyIf :: Bool -> (a -> a) -> a -> a
applyIf True f x = f x
applyIf False _ x = x
-- Or, how I'd probably actually write it:
-- applyIf True = id
-- applyIf False = flip const
-- Note that `flip f a b = f b a` and `const a _ = a`, so
-- `flip const = \_ a -> a` returns its second argument.
example1' = applyIf applyFilter (when someFilter) body
example2' = applyIf runOnThread (void . forkIO) . forM_ [1..10] $ \i ->
print i >> threadDelay 1000000
And then, of course, if some particular use of applyIf
was a common pattern in your application, you could abstract over it:
-- Runs its argument on a separate thread if the application is configured to
-- run on more than one thread.
possiblyThreaded action = do
multithreaded <- (> 1) . numberOfThreads <$> getConfig
applyIf multithreaded (void . forkIO) action
example2'' = possiblyThreaded . forM_ [1..10] $ \i ->
print i >> threadDelay 1000000
As mentioned above, Haskell is certainly not alone in being able to express this idea. For instance, here's a translation into Ruby, with the caveat that my Ruby is very rusty, so this is likely to be unidiomatic. (I welcome suggestions on how to improve it.)
def apply_if(use_function, f, &block)
use_function ? f.call(&block) : yield
end
def example1a
do_when = lambda { |&block| if some_filter then block.call() end }
apply_if(apply_filter, do_when) { puts "Hello, world!" }
end
def example2a
apply_if(run_on_thread, Thread.method(:new)) do
(1..10).each { |i| puts i; sleep 1 }
end
end
def possibly_threaded(&block)
apply_if(app_config.number_of_threads > 1, Thread.method(:new), &block)
end
def example2b
possibly_threaded do
(1..10).each { |i| puts i; sleep 1 }
end
end
The point is the same—we wrap up the maybe-do-this-thing logic in its own function, and then apply that to the relevant block of code.
Note that this function is actually more general than just working on code blocks (as the Haskell type signature expresses); you can also, for instance, write abs n = applyIf (n < 0) negate n
to implement the absolute value function. The key is to realize that code blocks themselves can be abstracted over, so things like if statements and for loops can just be functions. And we already know how to compose functions!
Also, all of the code above compiles and/or runs, but you'll need some imports and definitions. For the Haskell examples, you'll need the impots
import Control.Applicative -- for (<$>)
import Control.Monad -- for when, void, and forM_
import Control.Concurrent -- for forkIO and threadDelay
along with some bogus definitions of applyFilter
, someFilter
, body
, runOnThread
, numberOfThreads
, and getConfig
:
applyFilter = False
someFilter = False
body = putStrLn "Hello, world!"
runOnThread = True
getConfig = return 4 :: IO Int
numberOfThreads = id
For the Ruby examples, you'll need no imports and the following analogous bogus definitions:
def apply_filter; false; end
def some_filter; false; end
def run_on_thread; true; end
class AppConfig
attr_accessor :number_of_threads
def initialize(n)
@number_of_threads = n
end
end
def app_config; AppConfig.new(4); end
Upvotes: 6
Reputation: 91942
Common Lisp does not let you redefine if
. You can, however, invent your own control structure as a macro in Lisp and use that instead.
Upvotes: 2