Могу ли я использовать шаблон haskell для определения недостающих функций?

У меня есть ситуация, когда мне нужно скомпилировать код Haskell на разных машинах. По крайней мере, одна из этих машин имеет довольно старую версию Control.Concurrent.STM, которая не знает modifyTVar. Моим текущим решением является копирование кода для modifyTVar из новой версии пакета. Это заставило меня задуматься, можно ли использовать шаблон Haskell, чтобы проверить, определена ли функция и только определить ее, если она отсутствует. Я знаю, что правильное решение, вероятно, было бы для получения более свежих пакетов, но мне стало любопытно.

Ответ 1

Кажется, это возможно следующим образом. Сначала вспомогательный модуль:

{-# LANGUAGE TemplateHaskell #-}

module AddFn where

import Language.Haskell.TH

-- | Add a function if it doesn't exist.
addFn :: String -> Q [Dec] -> Q [Dec]
addFn name decl = do
    r <- lookupValueName name
    case r of
        Just l -> return []
        Nothing -> report False ("adding missing " ++ name) >> decl

и использовать его как в

{-# LANGUAGE TemplateHaskell #-}

module Main where

import AddFn
import qualified Data.Traversable as T

$(addFn "mapM"
    [d| mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
        mapM = T.mapM
    |])

$(addFn "mapM1"
    [d| mapM1 :: (Monad m) => (a -> m b) -> [a] -> m [b]
        mapM1 = T.mapM
    |])

Недостатком является то, что он использует lookupValueName, который находится только в последних версиях TH, поэтому при работе со старыми установками это, вероятно, не поможет. Возможно, возможным решением было бы вместо этого вызвать reify для данного имени и использовать recover для обработки случая, когда имя отсутствует.

Обновление:. Версия, использующая reify вместо lookupValueName, работает:

-- | Add a function if it doesn't exist.
addFn :: String -> Q [Dec] -> Q [Dec]
addFn name decl = recover decl (reify (mkName name) >> return [])

Ответ 2

Шаблон Haskell несколько завышен для этого - вы можете использовать CPP вместо этого, используя макросы MIN_VERSION, которые Cabal определит:

{-# LANGUAGE CPP #-}

#if MIN_VERSION_stm(2, 3, 0)
-- nothing
#else
modifyTVar = ...
#endif