1

有时,我有一个控制结构(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()) {
    //  ...
}

或者一个更复杂的例子,如果一个变量被设置在一个函数中包装代码并将它作为一个线程启动:

<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>

(实际上,在这个例子中,我可以想象给元条件命名甚至会很有用,以确保 top 和 bottom 同步。)

我可以想象这样的事情是 LISP 中的一个功能,对吧?

4

2 回答 2

6

任何具有一流功能的语言都可以做到这一点。实际上,您对“高阶”的使用是有说服力的;必要的抽象确实是一个高阶函数。这个想法是编写一个函数applyIf,它接受一个布尔值(启用/禁用)、一个控制流运算符(实际上只是一个函数)和一个代码块(函数域中的任何值);然后,如果布尔值为真,则将运算符/函数应用于块/值,否则块/值只是运行/返回。这在代码中会更加清晰。

例如,在 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) xf xif相同cond == True,并且与 else 相同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 示例,您将需要 impots

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
于 2013-02-18T03:17:23.033 回答
2

Common Lisp 不会让你重新定义if. 但是,您可以在 Lisp 中将自己的控制结构发明为宏并使用它。

于 2013-02-16T11:22:34.473 回答