有时,我有一个控制结构(if,for,...),并且根据条件我要么使用控制结构,要么只执行主体。举个简单的例子,我可以在C中做到以下几点,但这很难看:
#ifdef APPLY_FILTER
if (filter()) {
#endif
// do something
#ifdef APPLY_FILTER
}
#endif
如果我在运行时只知道apply_filter,它也不起作用。当然,在这种情况下,我只需将代码更改为:
if (apply_filter && filter())
但这在任意控制结构的一般情况下不起作用。 (我手边没有一个很好的例子,但最近我有一些代码可以从这样的功能中获益很多。)
是否有任何语言我可以应用条件来控制结构,即具有更高阶的条件?在伪代码中,上面的例子是:
<if apply_filter>
if (filter()) {
// ...
}
或者更复杂的例子,如果一个varable在函数中设置了包裹代码并将其作为一个线程启动:
<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>
(实际上,在这个例子中,我可以想象给元条件赋一个名称甚至是有用的,以确保顶部和底部s同步。)
我可以想象这样的东西是LISP中的一个功能,对吗?
答案 0 :(得分:6)
任何具有一流功能的语言都可以将其关闭。事实上,你对“高阶”的使用是有道理的;必要的抽象确实是一个更高阶的函数。我们的想法是编写一个函数applyIf
,它接受一个布尔值(启用/禁用),一个控制流操作符(实际上只是一个函数)和一个代码块(函数域中的任何值) ;然后,如果布尔值为true,则将运算符/函数应用于块/值,否则只运行/返回块/值。这将在代码中更加清晰。
例如,在Haskell中,这种模式在没有明确的applyIf
的情况下写成:
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
此处,id
只是身份函数\x -> x
;它总是返回它的论点。因此,如果(if cond then f else id) x
,则f x
与cond == True
相同,否则与id x
相同;当然,id x
与x
相同。
然后你可以将这个模式考虑到我们的applyIf
组合器中:
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
然后,当然,如果applyIf
的某些特定用法是您应用程序中的常见模式,那么您可以对其进行抽象:
-- 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
如上所述,Haskell当然不仅仅是能够表达这个想法。例如,这里是Ruby的翻译,但需要注意的是我的Ruby非常生疏,所以这很可能是单一的。 (我欢迎有关如何改进它的建议。)
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
重点是 - 我们在自己的函数中包含了可能做的事物逻辑,然后将其应用于相关的代码块。
请注意,此函数实际上比仅处理代码块更通用(因为Haskell类型签名表示);例如,您也可以编写abs n = applyIf (n < 0) negate n
来实现绝对值函数。关键是要意识到代码块本身可以被抽象出来,所以像if语句和for循环这样的东西可以只是函数。我们已经知道如何撰写函数了!
此外,上面的所有代码都编译和/或运行,但您需要一些导入和定义。对于Haskell示例,您将需要impot
import Control.Applicative -- for (<$>)
import Control.Monad -- for when, void, and forM_
import Control.Concurrent -- for forkIO and threadDelay
以及applyFilter
,someFilter
,body
,runOnThread
,numberOfThreads
和getConfig
的一些虚假定义:
applyFilter = False
someFilter = False
body = putStrLn "Hello, world!"
runOnThread = True
getConfig = return 4 :: IO Int
numberOfThreads = id
对于Ruby示例,您不需要导入和以下类似的虚假定义:
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
答案 1 :(得分:2)
Common Lisp不允许您重新定义if
。但是,您可以在Lisp中将自己的控制结构发明为宏,并使用它。