более простой - как я могу создать список членов эффекта во время выполнения? - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь создать функцию для генерации строкового представления эффектов во время выполнения.

- в общем модуле

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

Из repl с загруженным дочерним модулем 1 я могу получить следующее, чего, если бы я мог получить что-то похожее в неинтерпретированном коде, было бы достаточно, чтобы получить то, что я хочу:

> :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как предлагается в следующем:

Как я могу прочитать метаданные типа во время выполнения?

, но 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 голосов
/ 13 ноября 2018

Во-первых, учитывая (на уровне типа) список эффектов 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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...