Let's say I have a datatype:
data Term : Type -> Type where
Id : Term (a -> a)
...
App : Term (a -> b) -> Term a -> Term b
With a proof something is App
:
data So : Bool -> Type where
Oh : So True
isApp : Term a -> Bool
isApp (App x y) = True
isApp x = False
Is it possible to write a function that gets the first argument of an App
? I don't know how I'd type it, since the original argument type is lost:
getFn : (x : Term b) -> So (isApp x) -> ???
getFn (App f v) p = f
I could keep tags inside the Term
s indicating what types have been applied to it, but then I'd have to restrict myself to taggable types. Previously I'd have assume that's the only option, but so many magical things seems to happen in the land of dependent types that I thought I'd ask first.
(Agda examples would be welcome as well, though I'd prefer Idris ones!)
Sure. I don't have an Idris compiler on this machine, but here is a solution in Agda:
open import Data.Bool.Base
data Term : Set -> Set₁ where
Id : ∀ {a} -> Term (a -> a)
App : ∀ {a b} -> Term (a -> b) -> Term a -> Term b
data So : Bool -> Set where
Oh : So true
isApp : ∀ {a} -> Term a -> Bool
isApp (App x y) = true
isApp x = false
GetFn : ∀ {b} -> (x : Term b) -> So (isApp x) -> Set₁
GetFn Id ()
GetFn (App {a} {b} f x) _ = Term (a -> b)
getFn : ∀ {b} -> (x : Term b) -> (p : So (isApp x)) -> GetFn x p
getFn Id ()
getFn (App f v) p = f
You just need to explicitly discard the Id
cases at the type and value levels. GetFn (App f x)
then returns the desired type and getFn (App f x)
then returns the desired value.
The crucial part here is that when you write getFn (App f v)
x
gets unified with App f v
, the type signature becomes GetFn (App f v) p
, which simplifies to Term (a -> b)
for the appropriate a
and b
, which is exactly the type of f
.
Alternatively you can write
GetFn : ∀ {b} -> Term b -> Set₁
GetFn Id = junk where junk = Set
GetFn (App {a} {b} f x) = Term (a -> b)
getFn : ∀ {b} -> (x : Term b) -> So (isApp x) -> GetFn x
getFn Id ()
getFn (App f v) p = f
getFn
discards Id
, so we don't care about junk that GetFn
returns in the Id
case.
EDIT
I guess in Idris you will need to explicitly quantify over a
and b
in the App
constructor.