Can I use template haskell to define missing functions?
Asked Answered
O

2

11

I've got a situation where I need to compile some Haskell code on different machines. At least one of these machines has a rather old version of Control.Concurrent.STM, that doesn't know modifyTVar. My current workaround is to copy the code for modifyTVar from a newer version of the package. This got me wondering, if it would be possible to use template Haskell to check if a function is already defined and only define it, if it's missing. I'm aware that the proper solution would probably be to get more recent packages, but the situation got me curious.

Overcharge answered 15/6, 2014 at 19:33 Comment(3)
Why not just use CPP (checking the version of the stm package)?Companion
I wasn't aware this would have been an option. I tried using #ifdef, but kinda failed with it. Given my rather small knowledge of that topic I'm also not sure how to check a package version with it.Overcharge
I added an answer with the detailsCompanion
P
8

It seems to be possible as follows. First a helper module:

{-# 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

and use it as in

{-# 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
    |])

The drawback is that it's using lookupValueName, which is only in the recent versions of TH, so when dealing with old installations, this probably won't help. Perhaps a possible solution would be to instead call reify on a given name, and use recover to handle the case when the name is missing.

Update: The version using reify instead of lookupValueName works:

-- | Add a function if it doesn't exist.
addFn :: String -> Q [Dec] -> Q [Dec]
addFn name decl = recover decl (reify (mkName name) >> return [])
Prosser answered 15/6, 2014 at 21:25 Comment(0)
C
3

Template Haskell is somewhat overkill for this - you can use CPP instead, using the MIN_VERSION macros that Cabal will define:

{-# LANGUAGE CPP #-}

#if MIN_VERSION_stm(2, 3, 0)
-- nothing
#else
modifyTVar = ...
#endif
Companion answered 16/6, 2014 at 7:21 Comment(2)
I've got this running now, but I had to make two small adjustments, to get it working nicely with ghci as well. It was necessary to get ghci to include the cabal_macros.h. To do this I followed instructions from #19623037 and https://mcmap.net/q/337611/-ghci-configuration-fileOvercharge
With recent cabals, I think you can just use cabal replCompanion

© 2022 - 2024 — McMap. All rights reserved.