如何使用类型级别的可选字段表示数据?

时间:2016-07-06 16:37:26

标签: haskell

我正在处理具有值(多态,可以是其他)的控制流的数据,并且还可以具有验证值是否仍然良好的验证器函数,并且可以具有“刷新值”的功能(使用新值返回新数据)。

在vanilla Haskell中,它看起来像这样:

data MyData a = MyData
  {value :: a
  ,validator :: Maybe (a -> Bool)
  ,refresher :: Maybe (MyData a -> MyData a)}

我真正想要的是这些类型:

data Refreshable = Refreshable | NotRefreshable
data Validatable = Validatable | NotValidatable
MyData (r :: Refreshable) (v :: Validatable)

我已经做到了这一点,但仅限Refreshable。我想用Validatable来做,但是我遇到了构造函数的问题。仅针对Refreshable我需要有两个构造函数,一个用于可刷新数据,另一个用于不可刷新数据。有了可验证的,我需要有4个构造函数! (用于可刷新和可验证,用于不可刷新和可验证,用于可验证和不可刷新,以及用于不可刷新和不可验证的)。想象一下,如果我以后需要另一个可选字段。更糟糕的是:几乎所有的领域都是相同的,除了那些正在发生变化的领域,因此有太多的重复。

我也尝试用类型类/类型族修改情况 例如,MyData 'Refreshable 'NotValidatable只会变为Refreshable data => data,我可以实例MyData,或者只删除它以获取更多可以作为实例的特定数据。

这也有问题,因为它们不再是真正的领域了;即,我不能在没有验证器的情况下获取数据,并使用验证器将其更改为相同的数据(不在类型级别)。

这可能是一个XY问题;我认为更简洁的做法是制作Refreshable aValidatable a等数据类型,并在MyData中构建它们,但我不知道如何做到这一点。我无法包装它们,因为订单会改变一切。

有干净的方法吗?或者我应该坚持使用4个构造函数?或者Haskell还没准备好做这类事情呢? (没有双关语:P)。

2 个答案:

答案 0 :(得分:6)

这样的事情会满足你的要求吗?

import Control.Applicative (Const)
import Data.Functor.Identity

data MyData kv kr a = MyData
  {value :: a
  ,validator :: kv (a -> Bool)
  ,refresher :: kr (MyData a -> MyData a)}

-- examples
type FullData a      = Data Identity   Identity   a
type EmptyData a     = Data (Const ()) (Const ()) a
type ValidableData a = Data Identity   (Const ()) a

需要一些包装/展开(对于Identity)。

可以定义助记词别名type Present = Identitytype Missing = Const (),并附带一些扩展名。

可替换地,

data MyData (v :: Opt) (r :: Opt) a = MyData
  {value :: a
  ,validator :: Validator v a
  ,refresher :: Refresher r a}

data Opt = Yes | No

type family Validator (o :: Opt) a where
    Validator Yes = (a -> Bool)
    Validator No  = ()
-- etc.

-- examples
type FullData a      = Data Yes Yes a
type EmptyData a     = Data No  No  a
type ValidableData a = Data Yes No  a

答案 1 :(得分:2)

我最终得到了chi的回答:

data Opt = Yes | No

type family TOpt (o :: Opt) a where
  TOpt Yes a = a
  TOpt No a = ()

然后是这样的事情:

data MyData (v :: Opt) a = MyData
  {val :: a
  ,validator :: TOpt v (MyData 'Yes a -> Bool)
  ...
  }