问题
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:
- Do not use just a predicate
a -> Bool
for working with a subset type ofa
; create a view likeNotBaz
above. See the Idris tutorial on views, this post, and this answer for context. - Use
Dec
whenever possible instead of equality. Intutitively, you will be able to useDec
for predicates on types for which you can explicitly decide the truth of the predicate: seenotBaz
above. - 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