freer-simple-如何在运行时生成效果成员列表?

时间:2018-11-13 00:23:19

标签: haskell reflection

我正在尝试创建一个函数,以在运行时生成效果的字符串表示形式。

-在共享模块中

... 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

1 个答案:

答案 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类)中实现了自定义打印机。