我正在尝试创建一个函数,以在运行时生成效果的字符串表示形式。
-在共享模块中
... definitions for TestConfig, RunConfig
data GenericTest tc rc i effs as vs = GenericTest {
configuration :: tc,
components :: ItemClass i vs => TestComponents rc i effs as vs
} deriving Typeable
type Test = GenericTest TestConfig RunConfig
type EFFLogger effs = Member Logger effs
type EFFFileSystem effs = Members '[Logger, Ensure, FileSystem] effs
-子模块1
.... definitions for items, iterator etc
type Effects effs = EFFFileSystem effs
test :: forall effs. Effects effs => Test Item effs ApState ValState
test = GenericTest {
configuration = config {address = moduleOf ''ApState},
components = TestComponents {
testItems = items,
testInteractor = interactor,
testPrepState = prepState
}
}
-子模块2(与模块1相同,但效果不同)
.... definitions for items, iterator etc
type Effects effs = EFFLogger effs
test :: forall effs. Effects effs => Test Item effs ApState ValState
test = GenericTest {
configuration = config {address = moduleOf ''ApState},
components = TestComponents {
testItems = items,
testInteractor = interactor,
testPrepState = prepState
}
}
在运行时,我需要一个函数f
这样:
> f ChildMod1.test
> ["Logger", "Ensure", "FileSystem"]
>
> f ChildMod2.test
> ["Logger"]
从装有子模块1的repl中,我可以得到以下内容,如果我可以用未解释的代码得到类似的东西,那么就足以得到我想要的东西:
> :t test
> test
:: (Data.OpenUnion.Internal.FindElem Logger effs,
Data.OpenUnion.Internal.FindElem Ensure effs,
Data.OpenUnion.Internal.FindElem FileSystem effs) =>
Test Item effs ApState ValState
我尝试按照以下建议使用Typeable:
How can I read the metadata of a type at runtime?
但是typeOf给我带来了我不知道如何解决的问题:
> typeOf test
<interactive>:5:1-11: error:
* No instance for (Typeable effs0) arising from a use of `typeOf'
* In the expression: typeOf test
In an equation for `it': it = typeOf test
<interactive>:5:8-11: error:
* Ambiguous type variable `effs0' arising from a use of `test'
prevents the constraint `(Data.OpenUnion.Internal.FindElem
Logger effs0)' from being solved.
Probable fix: use a type annotation to specify what `effs0' should be.
These potential instances exist:
two instances involving out-of-scope types
instance [overlappable] Data.OpenUnion.Internal.FindElem t r =>
Data.OpenUnion.Internal.FindElem t (t' : r)
-- Defined in `Data.OpenUnion.Internal'
instance Data.OpenUnion.Internal.FindElem t (t : r)
-- Defined in `Data.OpenUnion.Internal'
* In the first argument of `typeOf', namely `test'
In the expression: typeOf test
In an equation for `it': it = typeOf test
答案 0 :(得分:2)
首先,给定效果(类型级别)列表effs
,我们可以通过Typeable
获取其字符串:
import Type.Reflection
showEffs :: forall effs. Typeable effs => String
showEffs = show (typeRep @effs)
现在问题出在函数f
上以其参数类型来获取约束。如您所见,幼稚的尝试将失败:f test
将专门化test
并向上传播约束,从而导致有关实例解析和模糊类型变量的错误。
更好的解决方案是将=>
替换为“可匹配”的常规数据类型。
newtype WithEffects_ es0 es1 a = WithEffects { unWithEffects :: Members es0 es1 => a }
类型同义词也需要一些重构。
type EFileSystem = '[Logger, Ensure, FileSystem]
type WithEffects = WithEffects_ EFileSystem
现在测试如下:
test :: forall effs. WithEffects effs (Test Item effs ApState ValState)
test = WithEffects $ ... -- the rest unchanged
,则需要用unWithEffects test
明确地将其解包。现在,我们可以从es0
中提取效果WithEffects_ es0 es1 a
的运行时表示形式:
import Type.Reflection
effsRepTest :: Typeable es0 => WithEffects_ es0 es1 a -> TypeRep es
effsRepTest _ = typeRep
showEffsTest :: Typeable es0 => WithEffects_ es0 es1 a -> String
showEffsTest = show . effsRepTest
因此,要提取表示es0
的字符串,我们可以编写:
showEffsTest test :: String
已编辑:您可能会在注释中找到该答案的旧版本的痕迹,该注释建议使用newtype c ==> a = Arr { unArr :: c => a }
,但在这里不起作用,因为Members
是类型族。因此,您需要另一种类型,例如es0
来更明确地携带效果列表WithEffects_
。
再次编辑:
以下是可编译的要点:https://gist.github.com/Lysxia/d7b6bdc23bcb43cb40439b7e037e8145
上面的答案实际上是这样打印的:
': (* -> *) Logger (': (* -> *) Ensure (': (* -> *) FileSystem ('[] (* -> *))))
为了获得更好的效果,我在该要点(ShowTypes
类)中实现了自定义打印机。