我想在dhall中表示IPv4地址,以便我可以管理主机配置。
默认情况下,它以文本形式保存;但这显然不能令人满意,因为它允许任何旧文本漏掉。我想将这些值保留为8位值的4元组。
我不认为Dhall可以本机允许-我能看到的最接近的记录是{a:Natural,b:Natural}等,但这在语法上是笨拙的,并且仍然允许八位组值在0之外-255。
假设我无法直接在Dhall中实现此目的,也许我可以在Haskell中定义一个可以自动从Dhall中读取自然值的4长度列表的值的类型,
我的问题是:
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
的文档似乎表明编号记录是一种方式去)。
有没有办法实现我的追求?
答案 0 :(得分:4)
对于IP地址,我建议在不支持该类型的语言的情况下,将它们表示为Dhall字符串。我建议这样做的主要原因有两个:
例如,如果这是关于日期/时间本机支持的问题,我会给出相同的答案(出于相同的原因)。
也就是说,我仍然会帮助您调试遇到的问题。我要做的第一件事是尝试使用更新版本的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