ファーストクラスの関数を備えた言語は、これをうまくやってのけることができます。実際、「高次」の使用は物語っています。必要な抽象化は、実際には高階関数になります。アイデアは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
ifと同じであり、それ以外の場合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
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