我如何在dhall中表示一个元组?

时间:2019-02-07 18:14:56

标签: haskell dhall

我想在dhall中表示IPv4地址,以便我可以管理主机配置。

默认情况下,它以文本形式保存;但这显然不能令人满意,因为它允许任何旧文本漏掉。我想将这些值保留为8位值的4元组。

我不认为Dhall可以本机允许-我能看到的最接近的记录是{a:Natural,b:Natural}等,但这在语法上是笨拙的,并且仍然允许八位组值在0之外-255。

假设我无法直接在Dhall中实现此目的,也许我可以在Haskell中定义一个可以自动从Dhall中读取自然值的4长度列表的值的类型,

我的问题是:

  1. 我是否认为在Dhall中直接执行此操作是不可能或不成比例地困难?
  2. 要在Haskell中定义此类型,我是否要定义Interpret的实例;如果是这样,我如何定义一个实例,该实例将读取一个由四部分组成的整数列表,同时为构造错误的列表(错误长度的列表,非整数列表或非列表的列表)提供有用的错误消息边界值(不介于0和255之间的整数)。

这是我尝试过的:

{-# LANGUAGE DeriveGeneric   #-}
{-# LANGUAGE RecordWildCards #-}

import Control.Applicative  ( empty, pure )
import Dhall  ( Generic, Interpret( autoWith ), Type( Type, extract, expected ) )
import Dhall.Core  ( Expr( Natural, NaturalLit ) )
import Data.Word  ( Word8 )

newtype IP = IP (Word8, Word8, Word8, Word8)
  deriving Generic

word8 :: Type Word8
word8 = Type {..}
  where
    extract (NaturalLit n) | n >= 0 && n <= 255 = pure (fromIntegral n)
    extract  _             = empty

    expected = Natural

instance Interpret Word8 where
  autoWith _ = word8

instance (Interpret a,Interpret b,Interpret c,Interpret d) => Interpret (a,b,c,d)

instance Interpret IP where

但是我正在努力寻找一种方法来表达可在dhall中读取的值:

λ> input auto "{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}" :: IO IP
*** Exception: 
Error: Expression doesn't match annotation

{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}

(input):1:1

(我宁愿将IP表示为[1,2,3,4];但是遵循错误消息和pair的文档似乎表明编号记录是一种方式去)。

有没有办法实现我的追求?

1 个答案:

答案 0 :(得分:4)

对于IP地址,我建议在不支持该类型的语言的情况下,将它们表示为Dhall字符串。我建议这样做的主要原因有两个:

  • 如果该语言原本就支持IP地址,那么它将为您的用户提供最流畅的迁移路径(只需删除引号即可)
  • 通常,总会存在语言无法完美建模以使无效状态无法表示的数据类型。如果数据类型非常适合Dhall的类型系统,则可以利用它,但是如果不能,请不要强行使用它,否则会令您和您的用户感到沮丧。 Dhall不一定是完美的。比YAML好。

例如,如果这是关于日期/时间本机支持的问题,我会给出相同的答案(出于相同的原因)。

也就是说,我仍然会帮助您调试遇到的问题。我要做的第一件事是尝试使用更新版本的dhall软件包重现此问题,因为该错误消息已得到改善:

*Main Dhall> input auto "{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}" :: IO IP
*** Exception: 
Error: Expression doesn't match annotation

{ + _2 : …
, + _3 : …
, + _4 : …
,   _1 : - { … : … }
         + Natural
}

{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural} : { _1 : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural } }
(input):1:1

错误消息现在显示“ type diff”,它说明两种类型的不同之处。在这种情况下,差异已经暗示了这个问题,那就是有一个额外的记录封装了该类型。它认为最外层应该只有一个_1字段,而我们期望的四个_1 / _2 / _3 / _4字段可能是嵌套的在该字段内(这就是为什么它认为_1字段存储记录而不是Natural的原因)。

但是,我们可以通过将内容包装在detailed函数中来获取更多细节,该函数与命令行上的--explain标志等效:

*Main Dhall> detailed (input auto "{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}" :: IO IP)
*** Exception: 
Error: Expression doesn't match annotation

{ + _2 : …
, + _3 : …
, + _4 : …
,   _1 : - { … : … }
         + Natural
}

Explanation: You can annotate an expression with its type or kind using the     
❰:❱ symbol, like this:                                                          


    ┌───────┐                                                                   
    │ x : t │  ❰x❱ is an expression and ❰t❱ is the annotated type or kind of ❰x❱
    └───────┘                                                                   

The type checker verifies that the expression's type or kind matches the        
provided annotation                                                             

For example, all of the following are valid annotations that the type checker   
accepts:                                                                        


    ┌─────────────┐                                                             
    │ 1 : Natural │  ❰1❱ is an expression that has type ❰Natural❱, so the type  
    └─────────────┘  checker accepts the annotation                             


    ┌───────────────────────┐                                                   
    │ Natural/even 2 : Bool │  ❰Natural/even 2❱ has type ❰Bool❱, so the type    
    └───────────────────────┘  checker accepts the annotation                   


    ┌────────────────────┐                                                      
    │ List : Type → Type │  ❰List❱ is an expression that has kind ❰Type → Type❱,
    └────────────────────┘  so the type checker accepts the annotation          


    ┌──────────────────┐                                                        
    │ List Text : Type │  ❰List Text❱ is an expression that has kind ❰Type❱, so 
    └──────────────────┘  the type checker accepts the annotation               


However, the following annotations are not valid and the type checker will
reject them:                                                                    


    ┌──────────┐                                                                
    │ 1 : Text │  The type checker rejects this because ❰1❱ does not have type  
    └──────────┘  ❰Text❱                                                        


    ┌─────────────┐                                                             
    │ List : Type │  ❰List❱ does not have kind ❰Type❱                           
    └─────────────┘                                                             


Some common reasons why you might get this error:                               

● The Haskell Dhall interpreter implicitly inserts a top-level annotation       
  matching the expected type                                                    

  For example, if you run the following Haskell code:                           


    ┌───────────────────────────────┐                                           
    │ >>> input auto "1" :: IO Text │                                         
    └───────────────────────────────┘                                           


  ... then the interpreter will actually type check the following annotated     
  expression:                                                                   


    ┌──────────┐                                                                
    │ 1 : Text │                                                                
    └──────────┘                                                                


  ... and then type-checking will fail                                          

────────────────────────────────────────────────────────────────────────────────

You or the interpreter annotated this expression:                               

↳   { _1 = 1, _2 = 2, _3 = 3, _4 = 5 }
  : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural }

... with this type or kind:                                                     

↳ { _1 : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural } }

... but the inferred type or kind of the expression is actually:                

↳ { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural }

────────────────────────────────────────────────────────────────────────────────

{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural} : { _1 : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural } }
(input):1:1

关键部分是消息的底部,内容为:

You or the interpreter annotated this expression:                               

↳   { _1 = 1, _2 = 2, _3 = 3, _4 = 5 }
  : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural }

... with this type or kind:                                                     

↳ { _1 : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural } }

... but the inferred type or kind of the expression is actually:                

↳ { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural }

...并确认包裹该类型的多余1字段记录正在干扰解码。

此意外类型的原因是由于您如何在此处派生Interpret的{​​{1}}实例:

IP

当您省略instance Interpret IP where 实例实现时,它会使用Interpret的{​​{1}}实例,而该实例与{{1} } Generic的实例。您可以通过要求GHC打印两种类型的通用表示来确认这一点:

IP

Generic类型的(Word8, Word8, Word8, Word8)表示形式是具有一个(匿名)字段的记录,其中一个字段包含*Main Dhall> import GHC.Generics *Main Dhall GHC.Generics> :kind! Rep IP Rep IP :: * -> * = D1 ('MetaData "IP" "Main" "main" 'True) (C1 ('MetaCons "IP" 'PrefixI 'False) (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 (Word8, Word8, Word8, Word8)))) *Main Dhall GHC.Generics> :kind! Rep (Word8, Word8, Word8, Word8) Rep (Word8, Word8, Word8, Word8) :: * -> * = D1 ('MetaData "(,,,)" "GHC.Tuple" "ghc-prim" 'False) (C1 ('MetaCons "(,,,)" 'PrefixI 'False) ((S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word8) :*: S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word8)) :*: (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word8) :*: S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Word8)))) s的4元组。 Generic类型的IP表示是4个字段的记录(每个字段包含一个Word8)。您可能希望后一种行为(最外层记录为4个字段)而不是前一种行为(最外层记录为1个字段)。

实际上,我们可以通过直接解码为Generic类型来获得您期望的行为:

(Word8, Word8, Word8, Word8)

...尽管那并不能真正解决您的问题:)

因此,如果您希望Word8类型具有与(Word8, Word8, Word8, Word8)相同的*Main Dhall GHC.Generics> detailed (input auto "{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}" :: IO (Word8, Word8, Word8, Word8)) (1,2,3,5) 实例,那么您实际上不希望使用GHC IP派生{{1 }}个Interpret的实例。您真正想要的是使用(Word8, Word8, Word8, Word8),以便Generics使用与基础类型完全相同的实例。您可以使用以下代码进行操作:

Interpret

我所做的主要更改是:

  • 添加IP语言扩展名
  • 删除GeneralizedNewtypeDeriving的{​​{1}}实例
  • newtype添加{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} import Control.Applicative ( empty, pure ) import Dhall ( Generic, Interpret( autoWith ), Type( Type, extract, expected ) ) import Dhall.Core ( Expr( Natural, NaturalLit ) ) import Data.Word ( Word8 ) newtype IP = IP (Word8, Word8, Word8, Word8) deriving (Interpret, Show) word8 :: Type Word8 word8 = Type {..} where extract (NaturalLit n) | n >= 0 && n <= 255 = pure (fromIntegral n) extract _ = empty expected = Natural instance Interpret Word8 where autoWith _ = word8 instance (Interpret a,Interpret b,Interpret c,Interpret d) => Interpret (a,b,c,d) 实例(用于调试)

...然后可行:

GeneralizedNewtypeDeriving

您也可以在没有任何孤立实例的情况下执行此操作,

Generic