如何在 Dhall 中表示 "Data.Map Text Text"?

How to represent "Data.Map Text Text" in Dhall?

如果我在 Haskell 中有这样的类型:

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

如何在 Dhall 中表示 MyType 的值?

{ env = ???
}

我想做的是在 Dhall 中写入 MyType 的值,然后从 Haskell 中读取它并将其解组为 MyType,如下所示:

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

我来自 Data.Aeson 和 YAML,您可以在其中表示这样的地图:

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

(您可以使用 Aeson 的 decodeFileEither 将上述内容解析为 MyType 类型)。

经过一番挖掘,我发现了三个解决方法。需要的话跳到底部 最好的解决方法,直到 toMap 土地。

截至 2019-05-05,无法像现在这样在 Dhall 中表示地图 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

以上改编自this answer,显示了一种方法 将 IP 地址解析为 4 元素元组。

为了解析成一个Map,我们可以这样做:

{-# 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

以上内容改编自此 comment。这个想法是让 看起来像 { mapKey = X, mapValue = Y} 可解析的记录,然后 将此类记录的任何列表转换为地图。注意我们如何支持任何价值 键入,而不仅仅是文本(因此我们可以将 MyType 中的 env 设为 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 新类型包装器的孤立实例:

{-# 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