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>

(実際、この例では、上位と下位の s が確実に同期するように、メタ条件に名前を付けると便利であると想像できます。)

このようなものが LISP の機能であると想像できますよね?

4

2 に答える 2

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) xf xifと同じであり、それ以外の場合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 の例では、インポットが必要です。

import Control.Applicative -- for (<$>)
import Control.Monad       -- for when, void, and forM_
import Control.Concurrent  -- for forkIO and threadDelay

applyFiltersomeFilterbodyrunOnThreadnumberOfThreads、およびのいくつかの偽の定義とともに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 に答える