How can I create a function that only accepts a subset of constructors of a type?

筅森魡賤 提交于 2021-02-07 06:50:20

问题


Let's say I have a type like this:

data Foo = Bar String | Baz | Qux String

I want to have a function like this:

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

As written, this compiles, but it's not total, as there are missing cases; in other words, get Baz is treated like a hole rather than as an expression that doesn't typecheck.

I want to replace that Foo in the type signature of get with something that specifies that the value must be either a Bar or a Qux. How can I express this subset of the Foo type?


回答1:


You could also mix the two approaches (by Kim Stiebel and Anton Trunov) and construct a helper data type. The type OnlyBarAndQux can only be constructed with values of Bar and Qux. For idris it is then possible to automatically infer a proof if this is the case when invoking 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"



回答2:


I'd follow the approach taken in the std library for List head, for example. This is basically what Markus wrote plus using Dec for witnessing that a Foo being not Baz is decidable:

%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")

Some comments about this:

  1. Do not use just a predicate a -> Bool for working with a subset type of a; create a view like NotBaz above. See the Idris tutorial on views, this post, and this answer for context.
  2. Use Dec whenever possible instead of equality. Intutitively, you will be able to use Dec for predicates on types for which you can explicitly decide the truth of the predicate: see notBaz above.
  3. auto implicit arguments can help reducing the visual/cognitive clutter at usage site



回答3:


There is more than one way to do this, but the easiest is probably to make Foo a type constructor that takes a parameter indicating whether it's a Foo with a String in it or not. In this example I have used a Bool as the parameter:

%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



回答4:


I'd go with Kim Stebel's answer (if changing Foo is an option, as observed by @Eduardo Pareja Tobes), but I'd like to show another way. You can use a subset type, which is the same thing as dependent pair:

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)) can be translated as "some f of type Foo, but not Baz".

To call get you need to provide a dependent pair of an element of type Foo and a proof that it is not equal to Baz, like so:

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


来源:https://stackoverflow.com/questions/45702926/how-can-i-create-a-function-that-only-accepts-a-subset-of-constructors-of-a-type

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!