如何创建仅接受类型构造函数子集的函数?

时间:2017-08-15 23:22:07

标签: idris

假设我有这样的类型:

data Foo = Bar String | Baz | Qux String

我希望有这样的功能:

get : Foo -> String
get (Bar s) = s
get (Qux s) = s

正如所写,这是编译,但并不完全,因为有缺失的案例;换句话说,get Baz被视为一个洞而不是一个没有进行类型检查的表达。

我想将Foo的类型签名中的get替换为指定值必须为BarQux的内容。 如何表达Foo类型的此子集?

4 个答案:

答案 0 :(得分:4)

你也可以混合两种方法(由Kim Stiebel和Anton Trunov)并构建一个辅助数据类型。类型OnlyBarAndQux只能使用BarQux的值构建。对于idris,如果在调用get时出现这种情况,则可以自动推断出证据:

module FooMe

data Foo = Bar String | Baz | Qux String

data OnlyBarAndQux: Foo -> Type where
    BarEy: OnlyBarAndQux (Bar s)
    QuxEx: OnlyBarAndQux (Qux s)

||| get a string from a Bar or Qux
total
get: (f: Foo) -> {auto prf : OnlyBarAndQux f} -> String
get (Bar s) {prf = BarEy} = s
get (Qux s) {prf = QuxEx} = s

-- Tests

test1: get $ Bar "hello" = "hello"
test1 = Refl

test2: get $ Qux "hello" = "hello"
test2 = Refl

-- does not compile
-- test3: get $ Baz = "hello"

答案 1 :(得分:3)

例如,我将遵循标准库中List head所采用的方法。这基本上是Markus wrote加上使用Dec来证明Foo不是Baz是可判定的:

%default total

data Foo = Bar String | Baz | Qux String

data NotBaz : Foo -> Type where
  IsBar: NotBaz(Bar z)
  IsQux: NotBaz(Qux z)

Uninhabited (NotBaz Baz) where
  uninhabited _ impossible

notBaz : (f : Foo) -> Dec (NotBaz f)
notBaz Baz      = No absurd
notBaz (Bar s)  = Yes IsBar
notBaz (Qux s)  = Yes IsQux

get: (f : Foo) -> {auto ok : NotBaz f} -> String
get (Bar s) { ok = IsBar } = s
get (Qux s) { ok = IsQux } = s

s: String
s = get (Bar "bar")

对此有一些评论:

  1. 不要使用 谓词a -> Bool来处理a的子集类型;创建一个视图,如上面的NotBaz。有关上下文,请参阅the Idris tutorial on viewsthis postthis answer
  2. 尽可能使用Dec而不是相等。在任何情况下,您可以使用Dec作为类型的谓词,您可以明确地决定谓词的真实性:请参阅上面的notBaz
  3. 自动隐式参数可以帮助减少使用网站的视觉/认知混乱

答案 2 :(得分:2)

有多种方法可以执行此操作,但最简单的方法可能是使Foo成为一个类型构造函数,该构造函数接受一个参数,指示它是否Foo String在它或不在。在此示例中,我使用Bool作为参数:

%default total

data Foo : Bool -> Type where
  Bar : String -> Foo True -- a Foo constructed with Bar will have type Foo True
  Baz : Foo False -- and a Foo constructed with Baz will have type Foo False
  Qux : String -> Foo True

get : Foo True -> String
get (Bar s) = s
get (Qux s) = s

答案 3 :(得分:2)

我会选择金斯特贝尔的回答(如果改变Foo是一个选项,正如@Eduardo Pareja Tobes所说的那样),但我想表现出另一种方式。您可以使用子集类型,这与依赖对相同:

total
get : (f ** Not (f = Baz)) -> String
get (f ** pf) with (f)
  get (f ** _)      | (Bar s) = s                      -- this is as before
  get (f ** contra) | Baz = void $ contra Refl         -- a contradictory case
  get (f ** _)      | (Qux s) = s                      -- this is as before

(f ** Not (f = Baz))可以翻译为“某些f类型Foo,但不能翻译为Baz”。

要致电get,您需要提供类型为Foo的元素的从属对,并且证明它不等于Baz,如下所示:

s : String
s = get (Bar "bar" ** \Refl impossible)