[Agda] Pie in the sky
Alan Jeffrey
ajeffrey at bell-labs.com
Sun Sep 26 22:44:38 CEST 2010
Hmm, it tuns out you can get tantalizingly close to being able to code
up default arguments in existing Agda. See the attached file, which
defines a function whose type in the below syntax is:
ex : (?0 : Nat) -> Nat -> Nat
and can be used, for example with the default value:
map ex [ 37 ]
or we can override the default, using angle-brackets to indicate an
override:
ex < 5 > 37
Rather annoyingly, it doesn't infer a type for:
ex 37
which is something weird about when inference can deduce that an
expression has function type -- apparently just being applied to a value
is not enough!
Note, this is for the version where defaults are typed at
declaration-time, not usage-time. Oh, and the surface syntax is
rubbish, I only did the non-dependent level-monomorphic version, etc. etc.
Perhaps we don't need a language extension after all...
A.
On 09/25/2010 09:29 PM, Jeffrey, Alan S A (Alan) wrote:
> On 09/25/2010 12:22 AM, Samuel Gélineau wrote:
>> On 2010-09-24 at 9:44 PM, Alan Jeffrey wrote:
>>> Yes, but the default is provided by the *type* of f, not by the name of f,
>>> so when the type inference system deduces the type of f, this will cause new
>>> implicit arguments to be filled in, which may cause further type inference.
>>
>> My understanding was that the signature was a nice place to write the
>> defaults, but that adding them should in no way change the function's
>> type. If f and g both have the same signature except for their choice
>> of default, do you really want them to have distinct types? If f has
>> type ((x y : T) -> {x==y ? refl : x == y} -> U) and foo asks for a
>> value of type ((x y : T) -> {_ : x == y} -> U), do you really want
>> (foo f) not to type-check?
>
> Let's say you put the default argument into the function itself, rather
> than the function's type, something like:
>
> lambda {x ? M : T} -> N with type {x : T} -> U
>
> then eta-beta-equivalence gives:
>
> lambda {x ? M : T} -> N
> =eta= lambda {x : T} -> ((lambda {x ? M : T} -> N) x)
> =beta= lambda {x : T} -> N
>
> and oh dear, the default has disappeared. Eta-beta on functions is so
> hard-wired into dependent type theory that I'd be worried about messing
> with it.
>
> And yes, you'd need (foo f) in your example not to typecheck. Let's say
> foo = lambda g -> M -- this gets its implicit parameters resolved at
> compile time, say to give lambda g -> M'. But M[f/g] would resolve the
> implicit parameters differently, so we'd have foo f having different
> semantics than M[f/g], thus breaking beta-equivalence.
>
>> If you do insist on defaults being part of the type, then I have an
>> alternate proposal for you.
>
> Hmm, that's interesting -- a type (? d : A), I hadn't thought of it that
> way round. I'm not sure how it would interact with the constraint
> satisfaction algorithm for type inference -- when given a constraint X :
> (? Y : Z), at what point do you transform this to the constraints X = Y,
> X : Z?
>
> If the primitive is {x ? M : T} -> U, then the point that M is
> introduced is the point at which the constraint satisfaction algorithm
> decides that an implicit argument needs to be introduced. (Which it
> must be said is slightly mysterious to me, and indeed I've caught the
> type inference system being rather opportunistic about introducing
> implicit arguments before.)
>
>> If the types A and (? d : A) really are different, there should be a
>> mechanism to extract a value of type A from a value v of type (? d :
>> A). Call this mechanism (val v). There should also be a mechanism to
>> convert a value of type A into a value of type (? d : A), for those
>> times when d does not have the right type and you need to pass in a
>> value x of type A explicitly. Call this mechanism (explicit x). And
>> while we're at it, let's throw in (implicit p), for those times when
>> the value p proves that the default value d does have the correct
>> type.
>>
>> My proposal is to add two new builtins, (_﹖_∶_) and val, defined as follows.
>>
>>
>> infix 0 _﹖_∶_
>> data _﹖_∶_ (CommonA : Set) (default : CommonA) (A : Set) : Set where
>> explicit : A → CommonA ﹖ default ∶ A
>> implicit : CommonA ≡ A → CommonA ﹖ default ∶ A
>>
>> val : ∀ {CommonA default A} → CommonA ﹖ default ∶ A → A
>> val (explicit a) = a
>> val .{A} {default} {A} (implicit refl) = default
>>
>>
>> Whenever Agda infers that an omitted parameter x has type (CommonA ﹖
>> default ∶ A), it would try {x = implicit refl} instead of {x = _}. The
>> solves a meta without introducing new ones, so it shouldn't affect
>> termination. Using the new builtins would look as follows.
>>
>>
>> f : (x y : ℕ) → {x≡y : x ≡ x ﹖ refl ∶ x ≡ y} → suc x ≡ suc y
>> f x y {x≡y} with val x≡y
>> f x .x {_} | refl = refl
>>
>> foo : suc (2 + 3) ≡ suc (1 + 4)
>> foo = f (2 + 3) (1 + 4)
>>
>> bar : ∀ x y → x ≡ y → suc x ≡ suc y
>> bar x y p = f x y {explicit p}
>>
>>
>>> Could be. I'm still worried about nontermination. Of course, we still know
>>> soundness, because type checking is terminating, and pragmatically there's
>>> not much difference between nontermination and trying to typecheck a model
>>> of category theory :-)
>>
>> I find it funny that we're working in a language where every function
>> has to pass a termination checker, yet each language extension gets
>> scrutinized to make sure they are terminating. Somebody should write a
>> provably-terminating Agda compiler in Agda; this would make it much
>> easier to ensure that further language extensions preserve
>> termination.
>>
>> – Samuel
>
> A.
>
-------------- next part --------------
open import Data.Bool
open import Data.Nat
open import Data.List
module Defaults where
data Just (A : Set) : Set where
just : A → (Just A)
-- Only doing the non-dependent, level-monomorphic version right now
Default' : (A B C : Set) → Bool → Set
Default' A B C true = Just (A → B → C)
Default' A B C false = (B → C)
Default : (A B C : Set) → Set
Default A B C = {b : Bool} → (Default' A B C b)
default : {A B C : Set} → A → (A → B → C) → (Default A B C)
default a f {true} = just f
default a f {false} = f a
_⟨_⟩_ : {A B C : Set} → (Just (A → B → C)) → A → B → C
just f ⟨ a ⟩ b = f a b
-- Example: a binary function on integers with default value zero.
ex : Default ℕ ℕ ℕ
ex = default 0 (_+_)
-- Use the default value
ex0 : ℕ → ℕ
ex0 = ex
-- Override the default value
ex1 : ℕ
ex1 = ex ⟨ 5 ⟩ 37
-- Grumble mutter doesn't do inference properly
ex2 : ℕ
ex2 = ex 37
-- Does do inference property when put in a function-only position
ex3 : List ℕ
ex3 = map ex [ 37 ]
More information about the Agda
mailing list