模型选择性使用和类型

时间:2018-02-14 11:15:15

标签: haskell

我不确定如何正确建模。考虑总和类型

data Choice
    = A
    | B
    | C

和两种类型

data Foo = Foo
    { name : String
    , choice : Choice
    }

data Bar = Bar
    { name : String
    , choice : Choice
    }

因为它们是平等的。我想强制Foo只能使用AB作为Choice,而Bar只能使用B和{{1} }}

目前我解决了这个分裂C的问题,比如

Choice

但它看起来并不是一种可扩展的方法。是否有更好和/或更优雅的解决方案?

2 个答案:

答案 0 :(得分:3)

可能的方法是使用GADT如下。但是,这不是一般性的,因为构造函数可以与单个类型(例如ForFoo,ForBar)或任何类型(使用forAny作为类型变量)关联。

data Purpose = ForFoo | ForBar

data Choice (p :: Purpose) where
    A :: Choice 'ForFoo
    B :: Choice forAny  -- can be any Purpose
    C :: Choice 'ForBar

data Foo where
    Foo :: { nameF :: String , choiceF :: Choice 'ForFoo } -> Foo

data Bar where
    Bar :: { nameB :: String , choiceB :: Choice 'ForBar } -> Bar

如果我们需要表示选择Foo,Bar但不是Baz,则无法使用此方法。

更通用的方法可能是:

data Choice2 (a::Bool) (b::Bool) (c::Bool) where
    A2 :: Choice2 'True 'False 'False
    B2 :: Choice2 'False 'True 'False
    C2 :: Choice2 'False 'False 'True

data Foo2 where
    Foo2 :: { nameF2 :: String
            , choiceF2 :: Choice2 b1 b2 'False  } -> Foo2

data Bar2 where
    Bar2 :: { nameB2 :: String
            , choiceB2 :: Choice2 'False b1 b2 } -> Bar2

data Baz2 where
    Baz2 :: { nameZ2 :: String
            , choiceZ2 :: Choice2 b1 'False b2 } -> Baz2

原则上,这允许人们表达任何“子集”要求。在我们不想要的情况下,我们只需要False,并在其他情况下使用(存在量化的)变量。

它仍然不完全一般,并且有其缺点。例如,我们应该能够为

编写一个恒定时间的实现
coerce :: [Choice2 'True b1 b2] -> [Choice2 'True 'False 'False]

但我们不能,因为我们没有真正的亚型。

答案 1 :(得分:1)

模块化总和

如何使用通用和类型Either

data A = A
data B = B
data C = C

type (+) = Either

data Foo = Foo
  { name :: String
  , choice :: A + B
  }

data Bar = Bar
  { name :: String
  , choice = B + C
  }

模块化很好,但在构造和模式匹配值时,必须记住使用LeftRight的正确组合并不是很方便。此外,添加案例需要更改以前存在的模式。

然而,这种技术的变体可以解决这个问题。 基本上,使用类型类来选择/注入一个变量。例如,参见乙烯基中的Data.Vinyl.CoRec,其中match函数可以对变体进行模式匹配,而无需担心顺序或嵌套。在评论中,我有一个与prism和total库类似的代码片段。

平面参数化和

另一种似乎更接近你最初想法的方法是声明一个和类型,但是类型参数可以选择性地禁止某些构造函数。

chi已经举了一个使用GADT的例子。这是一个有常规ADT的人。

data Choice a b c
  = A a
  | B b
  | C c

如果没有A选项,只需设置a ~ Void(来自Data.Void),这是一个空类型。

data Foo = Foo
  { name :: String
  , choice :: Choice () () Void
    -- Two inhabitants: (A ()), (B ())
  }

我们仍然必须在C上进行模式匹配以避免出现警告,但使用-XEmptyCaseData.Void.absurd系统地调度案例。

case c :: Choice () () Void of
  A () -> 1
  B () -> 2
  C v -> case v of {}
  -- or  absurd v

这种技术也允许一些子类型。

A () :: forall b c. Choice () b c
     :: Choice () () ()
     :: Choice () Void Void
     :: Choice () () Void

缺点是支持新的(可选)选择意味着更改类型参数的数量,这很麻烦。