对于 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
错误消息现在显示“类型差异”,它解释了这两种类型的不同之处。在这种情况下,差异已经暗示了问题所在,即有一个额外的记录包装了该类型。它认为最外层应该只有一个字段,_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
实例的方式IP
:
instance Interpret IP where
当您省略实例Interpret
实现时,它会使用与. 您可以通过要求 GHC 打印出这两种类型的通用表示来确认这一点:Generic
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))))
该类型的Generic
表示IP
是具有一个(匿名)字段的记录,其中一个字段包含Word8
s 的 4 元组。类型的Generic
表示(Word8, Word8, Word8, Word8)
是 4 个字段的记录(每个字段都包含一个Word8
)。您可能期望后一种行为(4 个字段的最外层记录)而不是前一种行为(1 个字段的最外层记录)。
事实上,我们可以通过直接解码成一个(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)
...虽然这并不能真正解决您的问题:)
因此,如果您希望该IP
类型具有与该类型相同的Interpret
实例,那么(Word8, Word8, Word8, Word8)
您实际上并不想使用 GHCGenerics
派生. 您真正想要的是使用与基础类型完全相同的实例。您可以使用以下代码执行此操作:Interpret
IP
GeneralizedNewtypeDeriving
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
实例IP
- 为(用于调试)添加
Show
实例IP
...然后就可以了:
*Main Dhall GHC.Generics> input auto "{ _1 = 1, _2 = 2, _3 = 3, _4 = 5 } : { _1 : Natural, _2 : Natural, _3 : Natural, _4 : Natural}" :: IO IP
IP (1,2,3,5)
您也可以在没有任何孤立实例的情况下执行此操作,如下所示:
{-# LANGUAGE RecordWildCards #-}
import Control.Applicative (empty, pure)
import Data.Coerce (coerce)
import Dhall (Interpret(..), Type(..), genericAuto)
import Dhall.Core (Expr(..))
import Data.Word (Word8)
newtype MyWord8 = MyWord8 Word8
word8 :: Type MyWord8
word8 = Type {..}
where
extract (NaturalLit n)
| n >= 0 && n <= 255 = pure (MyWord8 (fromIntegral n))
extract _ =
empty
expected = Natural
instance Interpret MyWord8 where
autoWith _ = word8
newtype IP = IP (Word8, Word8, Word8, Word8)
deriving (Show)
instance Interpret IP where
autoWith _ = coerce (genericAuto :: Type (MyWord8, MyWord8, MyWord8, MyWord8))