Шаблон Haskell и неявные параметры
Есть ли способ создать функции с неявными параметрами или разрешить привязки с неявными параметрами, используя шаблон haskell?
Т.е. возможно ли сгенерировать такую подпись, используя шаблон haskell:
doSomething :: (?context :: Context) => m a
Или такой вызов:
invoc = let ?context = newContext in doSomething
Я не смог найти подходящих алгебраических типов данных или каких-либо функций, которые помогли бы мне по этой теме в документации API для шаблона haskell. Я использую GHC 7.4.2.
Если в haskell шаблона нет встроенной поддержки этого расширения, есть ли какая-то другая возможность внедрить код во время компиляции (может быть, что-то вроде общей "функции внедрения кода" в шаблоне haskell?).
РЕДАКТИРОВАТЬ: Я попробовал предложение из комментариев, вот что происходит:
runQ [d| f :: (?c :: String) => Int ; f = 7 |]
<interactive>:10:17: parse error on input `c'
тогда как это работает:
runQ [d| f :: Int ; f = 7|]
[SigD f_0 (ConT GHC.Types.Int),ValD (VarP f_0) (NormalB (LitE (IntegerL 7))) []]
кажется, не поддерживается.
1 ответ
Вот один способ, который довольно хрупок, но вроде работает. Хотя вы не можете ссылаться на? X в Exp, который использует шаблон haskell, вы можете ссылаться на определение в другом модуле, например:
reserved_prefix_x = ?x
Ниже приведен код, который генерирует переменные, подобные приведенным выше, за один прогон ghc, а во втором прогоне ghc переменные фактически ссылаются на неявные параметры.
{-# LANGUAGE TemplateHaskell, NoMonomorphismRestriction #-}
module GenMod (h) where
import Data.Generics
import Data.IORef
import Data.List
import Language.Haskell.Meta.Parse as P
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import qualified Data.Set as S
import qualified Language.Haskell.Exts.QQ as Q
import System.IO.Unsafe
h = Q.hs { quoteExp = \s -> do
r <- either fail (upVars . return) (P.parseExp s)
writeMod'
return r
}
pfx = "q_"
{-# NOINLINE vars #-}
vars :: IORef (S.Set String)
vars = unsafePerformIO (newIORef S.empty)
writeMod' = runIO $ writeFile "GEN.hs" . ppMod =<< readIORef vars
writeMod = -- might be needed to avoid multiple calls to writeFile?
-- in this example this is called for every use of `h'
QuasiQuoter { quoteDec = \ _ -> do
writeMod'
[d| _ = () |] }
ppMod xs = "{-# LANGUAGE NoMonomorphismRestriction, ImplicitParams #-}\n\
\module GEN where\n" ++
unlines (map (\x -> pfx ++ x ++ " = ?" ++ x) (S.toList xs))
upVars x = do
x' <- x
runIO $ modifyIORef vars (S.union (getMatchingVars x'))
runIO $ print =<< readIORef vars
return x'
getMatchingVars =
everything
S.union
(mkQ S.empty
(\ (OccName x) -> maybe S.empty S.singleton (stripPrefix pfx x)))
Файл Main.hs, который использует квазиквотер GenMod.hs:
{-# LANGUAGE NoMonomorphismRestriction, ImplicitParams, QuasiQuotes, TemplateHaskell, CPP #-}
import GenMod
#ifndef stage1
import GEN
#endif
f_ = [h| q_hithere |]
Вы должны вызвать GHC дважды, например:
ghci -Dstage1 Main.hs
GHCi, version 7.6.1: http://www.haskell.org/ghc/ :? for help
[1 of 2] Compiling GenMod ( GenMod.hs, interpreted )
[2 of 2] Compiling Main ( Ex.hs, interpreted )
fromList ["hithere"]
Ex.hs:8:6: Not in scope: `q_hithere'
Failed, modules loaded: GenMod.
Хотя ghc терпит неудачу, он все еще генерирует GEN.hs, который содержит:
{-# LANGUAGE NoMonomorphismRestriction, ImplicitParams #-}
module GEN where
q_hithere = ?hithere
Который будет там, когда вы загрузите Main (опуская флаг -D)
*Main> :t f_
f_ :: (?hithere::t) => t
Такая проблема, вероятно, не стоит того. Возможно, другие ситуации вызова в другие программы из TH более мотивирующие, такие как встроенные вызовы на другие языки http://hpaste.org/50837 (пример gfortran)
Поскольку я использовал синтаксический анализатор по умолчанию для haskell-src-meta, квазицитатура использует переменные "reserved_prefix_x", а не "? X". Должно быть возможно принять "х" без особых затруднений.