Closed Cmdv closed 2 years ago
Yeah, importing is a bit of a hack right now.
You can fix this particular example by importing everything from Types.Config
in Backend2Config
:
```Haskell
module Types.Carriers.Backend2Config where
import Theta.Target.Haskell (loadModule)
import Types.Config
loadModule "types" "carriers.backend2config"
fakeMessageConfig :: MessageConfig
fakeMessageConfig =
MessageConfig
{ message = fakeConfigCard
}
By default, loadModule
will generate all the types that it needs, including types from imported modules. In your example, the ConfigCard
type gets generated twice: once in Config.hs
and once in Backend2Config.hs
. This is where the error message is coming from—fakeConfigCard
uses the ConfigCard
type generated in Config.hs
, but MessageConfig
used the one generated in Backend2Config.hs
.
The way Template Haskell works is fundamentally on a per-module basis—as far as I know, it has no way to know about types defined in other modules unless they are imported and visible where the TH function (loadModule
) was called. This means that if you want to reuse the ConfigCard
type generated in Config.hs
, you fundamentally have to import it. I also couldn't figure out any way for TH to deal with qualified imports, so it'll have to be imported unqualified.
loadModule
has another limitation. As part of generating types for a Theta module (say com.example
), it generates a top-level identifier (theta'com'example :: Theta.Module
). Right now, it uses this identifier to avoid duplicates: if theta'com'example
is in scope, it'll skip generating anything for that module and assume that the Haskell types for com.example
are all in scope. So in your example, you would have to import not only ConfigCard
but also theta'config
.
It's a bit fiddly, but I couldn't figure out a better approach given Template Haskell's limitations. Thinking about it now, I could try checking whether a type name is in scope for every type I try to generate (rather than doing it on a per-module basis), but I'm not sure exactly how that would work, and it might make the generated HasTheta
instances inconsistent with each other.
ah lovely thanks you, yeah it's certainly a tricky one and I had a feeling it would be TH related 😓
Think what you've said to do is totally acceptable, we're just having to be a little more explicit where types are coming from. The confusion comes from not knowing exactly what TH is generating 😂
Didn't at all think about importing everything like you suggested! Doing so then bought Haskell language server into action and it recommended the following:
import Types.Config
( fakeConfigCard,
ConfigCard,
theta'config )
Purely out of interest could I ask what is theta'config
is ?
My guess is it's some sort of a TH pointer to the types inside config.theta
as without it the loadModule
from within Backend2Config
errors with a lots of Ambiguous occurrence ‘ConfigCard'...
. My guess is that makes the types concrete/parameterized as then TH knows that ConfigCard
does indeed come from config.theta
due to its use of loadModule
inside of Congig.hs
.... That's a massive guess though 😂
I'd say this isn't really a dramatic bug and the work around again isn't bad at all, I might make a little PR to explain that somewhere if you like? (any preference as to where?)
Purely out of interest could I ask what is
theta'config
is ?
theta'config
is the Theta.Module
object for the config
module. It contains the Theta type definitions, imports and metadata for each module. Theta types can reference each other, so we need to keep track of the module a type was defined in when we do anything with it. Each of the types generated in Haskell has a HasTheta
instance which maps the Haskell type to its Theta type, and having a single top-level theta'config
definition lets each of those Theta types point to exactly the same object describing the config
module.
So it mostly isn't a TH-specific definition—I originally needed it to connect generated Haskell types with the Theta types they were generated from. It then happened to be a convenient way to avoid generating duplicate types from loadModule
and that, without my intending it, made the imports work too, albeit in a slightly hacky way.
(As an aside, wish I could link to generated Haddock docs rather than the code itself, but I don't have that set up and I probably need to specify some version bounds before the package is ready to be uploaded to Hackage.)
I might make a little PR to explain that somewhere if you like? (any preference as to where?)
That would be great! There are a few places that could use some love in that respect:
Theta.Target.Haskell
loadModule
I'm honestly not sure which places make the most sense. I could see the same explanation working well in both the guide and one of the Haddock docs. In general, I've mostly kept the Haddock docs up-to-date—although there seem to be some small updates needed for the ones I linked—but the user guide is a bit less fresh, and updating it has been lingering on my mental to-do list for a while :P
This is magic! 🚀
thanks @TikhonJelvis will add something in the following few days. Bit snowed under at the moment 😅
@TikhonJelvis @Cmdv I believe we can close it now?
yeah can do, I didn't get the time to update docs sorry!!
Unsure if this is a bug or the expected behaviour but here is the scenario:
Two theta files and one imports from the other
types/carriers/backend2config.theta
:types/config.theta
Now two haskell files again doing a similar thing
haskell/Types/Carriers/Backend2Config.hs
:haskell/Types/Config.hs
inside of
Backend2Config.hs
I'm getting the following type error:Looks like maybe the parsing is unable to recognise something was imported from another location so give is a type of
ConfigCard
but that type isn't even generated when runningloadModule
so it becomes a bit of a no op type that exists but doesn't 😓I could have this totally the wrong way around and if there is a preferred method to doing what I'm trying to achieve that would be great.