Dfr
Dfr

Reputation: 4185

Dynamic module name

I want to do something like this in Haskell, but the compiler is not letting me.

Is there any way to accomplish this task?

-- both modules export function named "hello"
-- and I want to run it in every module
import qualified MyMod as M1
import qualified MyAnotherMod as M2

runmodules = map (\m -> m.hello) [M1, M2]

Upvotes: 7

Views: 359

Answers (3)

Anthony
Anthony

Reputation: 3791

I don't think you can quote a qualified name prefix like that in template haskell, and the hello identifier isn't in scope, so you might have to fall back to programming with strings.

module ModuleParamsTH where
import Language.Haskell.TH

getAll :: String -> [String] -> ExpQ
getAll valueName moduleNames = 
  listE $ map (varE . mkName . (++ suffix)) moduleNames
  where suffix = "." ++ valueName

which can then be used like so,

{-# LANGUAGE TemplateHaskell #-}
import ModuleParamsTH
import qualified ModuleParamsM1 as M1
import qualified ModuleParamsM2 as M2

runmodules = $(getAll "hello" ["M1", "M2"])

However, I would not do all this. You could just write [M1.hello, M2.hello] or use a type class to abstract over implementations.

Upvotes: 5

C. A. McCann
C. A. McCann

Reputation: 77424

Modules in Haskell are not even remotely first-class entities in the ways this would require, I'm afraid.

However, as bzn commented, Template Haskell can be used for problems like this. The result can be a bit clumsy, but if you really need some quick metaprogramming hacks it's not a bad choice. I'm not really an expert with TH, but what you want is pretty simple, with one catch: Neither "ambiguous identifiers" nor "module names" can be captured or quoted in any way, as far as I know, so you'll have to put them in strings given as arguments to the TH function.

Here's a quick and dirty, minimal example:

{-# LANGUAGE TemplateHaskell #-}
module MapModuleTH where

import Language.Haskell.TH

mapQual :: [String] -> String -> ExpQ
mapQual ms n = listE $ map (\m -> varE . mkName $ m ++ "." ++ n) ms

mapMQual :: [String] -> String -> ExpQ
mapMQual ms n = appE (varE 'sequence) $ listE $ map (\m -> varE . mkName $ m ++ "." ++ n) ms

You phrased things as "running the function" which sounds more like doing a bunch of IO actions, not just collecting a list of stuff, so I added a variant that also sequences the result.

Note that, despite the use of strings here, this is still statically typed--if the qualified names don't exist, or the types don't match up, you'll get the expected compile-time error just as if you'd written everything out by hand.

Here's a quick example of using it. Given the following:

{-# LANGUAGE TemplateHaskell #-}
module MapModule where

import MapModuleTH
import qualified Test1 as T1
import qualified Test2 as T2

tests = do xs <- $(mapMQual ["T1", "T2"] "test")
           putStrLn $ "Count: " ++ show (length xs)

Assuming the other modules are there and define test, then in GHCi we can see:

> tests
Test 1
Test 2
Count: 2

Upvotes: 8

jmg
jmg

Reputation: 7414

Modules aren't values in Haskell. Therefore that isn't possible. What do you want to achieve?

Upvotes: 3

Related Questions