Предоставить другое тело функции для универсальной функции на основе типа

Предположим, у меня есть какая-то общая функция

genericFunc :: a -> b
genericFunc x = doSomeHardWork

Но для определенного типа есть гораздо более эффективный способ genericFunc может быть сделано

genericFunc :: ParticularType -> b
genericFunc x = doSomeEasyWork

Каков наилучший способ объединить эти два функциональных тела в одно и то же genericFuncтакой, что при использовании на ParticularType, будет doSomeEasyWork, но при использовании на других типах, это будет doSomeHardWork? Я специально исключаю возможность использования другого имени или других модулей.

Я считаю, что это можно сделать с помощью класса типов, но меня больше интересуют решения, использующие языковые прагмы. У меня есть смутные предположения, что это можно сделать с помощью языковых прагм, но я понятия не имею, как. Бонусные баллы, если вы сравниваете и противопоставляете эти подходы и / или любые другие возможные подходы.

2 ответа

Решение

Это можно сделать с помощью классов типов, определив универсальный метод в определении класса и переопределив его в экземпляре. Переопределенная функция всегда будет использоваться.

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

С GHC вы можете использовать RULES прагма

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

Это будет применять правило перезаписи всякий раз, когда типы совпадают, и использовать обобщенную реализацию в противном случае.

Другие вопросы по тегам