Как представить «Data.Map Text Text» в Dhall? - PullRequest
1 голос
/ 05 мая 2019

Если у меня есть тип в Haskell, подобный этому:

data MyType = MyType
  { env :: Map Text Text
  }

Как я могу представить значение MyType в Dhall?

{ env = ???
}

Что я хочу сделать, этозаписать значения MyType в Dhall, а затем прочитать его из Haskell и распаковать в MyType, например:

main :: IO ()
main = do
    x <- input auto "./config"
    print (x :: MyType)

Я прихожу из Data.Aeson и YAML, где вы можете представлятькарты как это:

env:
  KEY1: "foo"
  KEY2: "bar"

(вы могли бы проанализировать вышеизложенное в тип MyType, используя decodeFileEither Эзона).

1 Ответ

1 голос
/ 05 мая 2019

После некоторых копаний я нашел три обходных пути. Перейдите на дно, если хотите лучший обходной путь до toMap земли.

По состоянию на 2019-05-05, в Дхалле нет возможности представлять карты, как они есть. возможно с Aeson / YAML (хотя поддержка нативной функции toMap скоро будет). Так что сейчас мы в основном должны использовать список однородных записей. Это немного неуклюже, но, по крайней мере, ты получаешь нативный маршалинг.

Если мы хотим использовать список кортежей вместо карты, мы можем сделать это:

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE GeneralizedNewtypeDeriving   #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE RecordWildCards     #-}

module Tuple where

import Dhall
import qualified Data.Text as T

data MyType = MyType { env :: [MyTuple] }
    deriving (Generic, Show)

instance Interpret MyType

newtype MyTuple = MyTuple (T.Text, T.Text)
    deriving (Interpret, Show)

-- input auto "{env = [{_1= \"HOME\", _2 = \"foo\"}] }" :: IO MyType

Выше было адаптировано из этого ответ , который показал способ разбирать IP-адреса как 4-элементные кортежи.

Для разбора на карту мы можем сделать:

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleInstances     #-}

module MapA where

import Data.Map (Map)
import Data.Text (Text)
import Dhall

import qualified Data.Map

data MyType = MyType { env :: Map Text Text }
    deriving (Generic, Show)

data KeyValue a = KeyValue { mapKey :: Text, mapValue :: a }
    deriving (Generic, Show)

toMap :: [KeyValue a] -> Map Text a
toMap keyValues = Data.Map.fromList (map adapt keyValues)
  where
    adapt (KeyValue {..}) = (mapKey, mapValue)

instance Interpret MyType
instance Interpret a => Interpret (KeyValue a)

-- Wrap `Map` in a newtype if you want to avoid an orphan instance
instance Interpret a => Interpret (Map Text a) where
    autoWith options = fmap toMap (autoWith options)

-- input auto "{env = [{mapKey = \"HOME\", mapValue = \"foo\"}] }" :: IO MapA.MyType

Выше было адаптировано из этого комментарий . Идея состоит в том, чтобы сделать записи, которые выглядят как { mapKey = X, mapValue = Y} разбираемые, а затем преобразовать любые списки таких записей в карту. Обратите внимание, как мы поддерживаем любую ценность введите, а не просто текст (поэтому мы можем иметь env in MyType be Map Text Int или что-то еще, если бы мы хотели). Это решение имеет только 1 тип переменной a для значений на карте, но я думаю, что можно сделать ключи более также общий.

ОК, так что после некоторой настройки я получил следующее для компиляции, которое поддерживает оба ключи и значения также должны быть общими:

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleInstances     #-}

module MapKV where

import Data.Map (Map)
import Data.Text (Text)
import Dhall

import qualified Data.Map

data MyType = MyType { env :: Map Text Text }
    deriving (Generic, Show)

data MyTypeInts = MyTypeInts { envInts :: Map Integer Integer }
    deriving (Generic, Show)

data KeyValue k v = KeyValue { mapKey :: k, mapValue :: v }
    deriving (Generic, Show)

toMap :: Ord k => [KeyValue k v] -> Map k v
toMap keyValues = Data.Map.fromList (map adapt keyValues)
  where
    adapt (KeyValue {..}) = (mapKey, mapValue)

instance Interpret MyType
instance Interpret MyTypeInts
instance (Interpret k, Interpret v) => Interpret (KeyValue k v)

-- Wrap `Map` in a newtype if you want to avoid an orphan instance
instance (Ord k, Interpret k, Interpret v) => Interpret (Map k v) where
    autoWith options = fmap toMap (autoWith options)

-- input auto "{env = [{mapKey = +1, mapValue = \"foo\"}] }" :: IO MapKV.MyType
-- input auto "{envInts = [{mapKey = +1, mapValue = -22 }] }" :: IO MapKV.MyTypeInts

Наконец, вот версия, в которой не используется экземпляр-сирота, использующий оболочку Env newtype:

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleInstances     #-}

module MapKV where

import Data.Map (Map)
import Dhall

import qualified Data.Map

data MyType = MyType { env :: Env }
    deriving (Generic, Show)

newtype Env = Env (Map Text Text)
  deriving (Eq, Generic, Show)

data KeyValue k v = KeyValue { mapKey :: k, mapValue :: v }
    deriving (Generic, Show)

toMap :: Ord k => [KeyValue k v] -> Map k v
toMap keyValues = Data.Map.fromList (map adapt keyValues)
  where
    adapt (KeyValue {..}) = (mapKey, mapValue)

instance Interpret MyType
instance (Interpret k, Interpret v) => Interpret (KeyValue k v)

instance Interpret Env where
    autoWith options = fmap (Env . toMap) (autoWith options)

-- input auto "{env = [{mapKey = \"HOME\", mapValue = \"foo\"}] }" :: IO MapKV.MyType
...