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?
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"
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:
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.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.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
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With