5

いくつかの一般的な関数があるとします

genericFunc :: a -> b
genericFunc x = doSomeHardWork

しかし、特定のタイプについては、はるかに効率的な方法genericFuncがあります。

genericFunc :: ParticularType -> b
genericFunc x = doSomeEasyWork

これら 2 つの関数本体を同じ に結合する最良の方法genericFuncParticularTypedoSomeEasyWorkですかdoSomeHardWork? 私は特に、別の名前または別のモジュールを使用するオプションを除外しています。

これは型クラスで実行できると思いますが、言語プラグマを使用するソリューションにもっと興味があります。これは言語プラグマで実行できるという漠然とした考えがありますが、方法がわかりません。これらのアプローチ、および/または他の可能なアプローチを比較対照すると、ボーナスポイントになります。

4

2 に答える 2

8

これは、クラス定義で汎用メソッドを定義し、インスタンスでオーバーライドすることにより、型クラスで実行できます。オーバーライドされた関数が常に使用されます。

class ContainsInt c where
  toList :: c -> [Int]

  -- generic function
  elem :: Int -> c -> Bool
  elem n x = Prelude.elem n (toList x)

instance ContainsInt () where
  toList _ = []

  -- Override the generic function for type ()
  elem _ _ = False

GHC でサポートされている代替手段は、書き換え規則を使用することです。書き換え規則は GHC に、可能な限りある式を別の式に置き換えるように指示します。置換の型が間違っている場合は置換されないため、これを使用して関数を特殊なバージョンに置換できます。書き換え規則は{-# RULES #-}プラグマで与えられます。

class ContainsInt c where
  toList :: c -> [Int]

elem :: ContainsInt c => Int -> c -> Bool
elem n x = Prelude.elem n (toList x)

-- Replace 'elem' by 'elemUnit' if it has the same type
{-# RULES "elem()" forall. elem = elemUnit #-}

elemUnit :: Int -> () -> Bool
elemUnit _ _ = False

書き換え規則はコンパイラの裁量で実行されるため、特定の状況で特殊な関数が呼び出される場合と呼び出されない場合があります。たとえば、書き換えは、コンパイラが関数をインライン化するかどうかによって異なります。

foo :: ContainsInt c -> Int -> [c] -> [Bool]
-- Must use the generic function
foo n cs = map (elem n) cs

useFoo :: Int -> [()] -> [Bool]
-- If 'foo' is inlined and 'elem' is not inlined, then this function will contain a rewritable call to 'elem'.
-- Otherwise rewriting cannot happen.
useFoo n cs = foo n cs
于 2011-12-11T21:24:53.663 に答える
2

RULESGHC では、プラグマを使用できます。

{-# RULES "genericFunc/easy" genericFunc = doSomeEasyWork #-}

これにより、型が一致するたびに書き換え規則が適用され、それ以外の場合はジェネリック実装が使用されます。

于 2011-12-11T21:17:07.273 に答える