It's difficult to design a language that has implicit arguments *and* allows you to explicitly instantiate them. Agda has done very admirably.

But I do not like bound names, which are often single letters, getting undeservedly elevated to names that play a descriptive role.

My maxim is:

If you see {A = A} in an argument list, they blew it.

There is something very honest about the way this is dealt with in Rocq, oversimplified below:

1. Implicitness of arguments is a "calling convention" that is configured for each defined function.

2. If you want to deviate from the implicit calling convention of a named function, you use an `@` sigil before the function, and then all arguments become explicit.

I like this a bit more because it discourages you from making things implicit that you frequently have to supply explicitly. Very quickly you learn whether your API is realistic in light of the unifier.

@jonmsterling

Discouraging users from overusing implicit arguments sounds reasonable (I am very guilty of this). I'm not convinced, however, in the edge cases where users do want to give some implicit arguments, that specifying them by position is any better than specifying them by name. Both are kinda arbitrary (especially in the presence of features like generalised variables, where the order in which variables are bound is really an implementation detail, though maybe this is a flaw in the design of generalised variables).

One idea I have wondered about is, inspired by bidirectional typechecking, allowing users to provide two implicitness-signatures for each definition: a synthesisable one and a checkable one. E.g. it would be nice to easily toggle between

refl : {A : Type} (x : A) -> x = x

and

refl : {A : Type} {x : A} -> x = x

but making A explicit is almost never desirable.

Of course in the presence of dependent types, metavariables and fancy unification, whether a particular argument is inferable is not a very local property, so this is probably overly simplistic, but I still think some approximation of it might be better than toggling all implicitness?

@NathanielB There are indeed some interesting points here that deserve some careful design.

Your example of two modes for 'refl' is very realistic, and I've had this come up in practice too. I think there is a chance to do this in a reasonable way…

Now, coming back to how to name things, I think that all this may be some evidence in favour of the idea of some form of keyword arguments. The idea would be to distinguish the *name* of the argument from the variable it binds. Then, one must use the *name* when explicitly instantiating an implicit argument.

(I'm an old Objective-C programmer, so I do keep reaching for Smalltalk-style method selectors.)

@NathanielB

> One idea I have wondered about is [...] allowing users to provide two implicitness-signatures for each definition

Rocq does allow the user to give more than one list of argument implicitness settings, distinguished by the arguments given in each case. As an example, in the Coq-HoTT library, we write `idpath` for the reflexivity path, and have
```
Arguments idpath {A a} , [A] a.
```
which says that `idpath` can be used with both arguments implicit and maximally inserted, or with `A` implicit and non-maximally inserted and `a` explicit. So for a goal of type `a = a`, I can write either `idpath` or `idpath a`, and both are accepted. The latter is useful in a context when Rocq doesn't know the type of paths a priori.

@jonmsterling

@jdchristensen @NathanielB Oh! I didn't realise you could have two different ones. That's a really good idea.
@jonmsterling you can also instantiate implicit arguments manually without resorting to @, using (A := bubu) just like in Agda. What I dislike though is that the common default for implicit arguments in Rocq is that they're maximally inserted.
Also, while we're talking about implicits, I really recommend trying out automatic variable generalization using backticks (see https://rocq-prover.org/doc/V9.2.0/refman/language/extensions/implicit-arguments.html#implicit-generalization).
Implicit arguments — The Rocq Prover 9.2 documentation

@jpoiret @jonmsterling I am not sure that it is correct to say that Rocq's default is maximal insertion. This is a choice that you make: did you declare the binding with '{}' or with '[]'?
@dgb37 @jonmsterling it is for generalized variables in binders, which is what I almost always use. I guess that could be changed though.
@jonmsterling it's actually very easy to have the elaborator tell you whether your implicit arguments are realistic https://gist.github.com/plt-amy/ef4722f0bc12895fb1c2b48f3bad66e4 but nobody took the bait of finishing this patch into a proper contribution so I'll have to do it myself eventually
0001-sketch-check-whether-declared-types-are-ambiguous.patch

GitHub Gist: instantly share code, notes, and snippets.

Gist
@jonmsterling A frustration that I find with Rocq is that, for inductives and records, often one wants the parameters(/indices) to be explicit for the type constructor but implicit for the data constructors/projectors. There are solutions to this but none are particularly elegant.
@jonmsterling This is a semi-related question: is it possible to check "generically" whether implicit arguments are inferable given that a function is applied to arguments with fully synthesized types? This could be a useful feature to have.
@olynch That's a very interesting question. I'm not sure... it sounds kind of plausible though that something like this might be possible :)

@olynch @jonmsterling

I think a conservative check would sometimes be too restrictive. Consider

bind : {M : Type -> Type} {A B : Type} -> Monad M -> (A -> M B) -> M A -> M B

In practice, this definition is usually fine because M will be instantiated with some (definitionally) injective type former like List, and problems like List ?B = List Int are solvable, but technically we could instantiate M with a constant family like \_. Nat, and now inferring B is hopeless. Having to make B an explicit arguments to bind, merely to defend against this edge-case, would kinda suck IMO...

@NathanielB @jonmsterling Interesting! Yeah makes sense that you'd need something like injectivity to make sense of this.
@olynch @NathanielB @jonmsterling Haskell-style usage of typeclasses only supports reasonably strong and stable inference if there's a way to specify functions with definitionally injective application. "return True" infers "m Bool", where "Monad m", and if we check that against "Maybe Bool", we want to deduce "m = Maybe". So in a TT we should declare the Monad class with injective functions for the parameter. Practically this requires instances to be formal type constructors but not random functions.

@AndrasKovacs Would it make sense/be possible to have a dedicated ‘injective’ function space in the type system for this?

@olynch @NathanielB @jonmsterling

@constantine @olynch @NathanielB @jonmsterling Yes, that's the proper way IMO.
@AndrasKovacs @constantine @NathanielB @jonmsterling OK neat; I wonder how this integrates with the approach to nominality via kinetic/potential elements (https://owenlynch.org/blog/nominal-types-via-energies/).
Nominal Types via Energies - Owen Lynch

@AndrasKovacs @constantine @olynch @NathanielB Very curious about how this works…

@jonmsterling @AndrasKovacs @olynch @NathanielB i guess it is only introduced by type constructors and has an application rule (and no computation rules). You can wrap it in a normal lambda to recover the usual function space (and probably elaboration can do this automatically).

Though I wonder if it’s possible to do better..

@constantine @jonmsterling @AndrasKovacs @olynch @NathanielB This proposal sounds similar to how GHC tracks injectivity of type-level functions, and GHC additionally allows the user to manually mark type-level functions as injective (and GHC checks it).

@zyang That suggests you could also have lambdas for this function space as long as the body is injective with respect to the bound variable.

@jonmsterling @AndrasKovacs @olynch @NathanielB

@constantine @jonmsterling @AndrasKovacs @olynch @NathanielB Yes, the syntax for doing this in GHC is like

```
type family MyInjFun (a :: Type) = (result :: Type) | result -> a where
MyInjFun a = Maybe (Maybe a)
```
The syntax looks indeed rather unintuitive...

@zyang @constantine @jonmsterling @olynch @NathanielB The system I have in mind is closer to the unsaturated type families proposal: https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0242-unsaturated-type-families.rst Currently we can't abstract over injective type families, we can only mark specific type families as injective.
ghc-proposals/proposals/0242-unsaturated-type-families.rst at master · ghc-proposals/ghc-proposals

Proposed compiler and language changes for GHC and GHC/Haskell - ghc-proposals/ghc-proposals

GitHub

@jonmsterling I learned that one the hard way a few sessions back, yeah (I'm not spending enough time coding: you'll be getting a fair idea of how often from here).

If I ever really want it for some reason, it's a trivial two-liner to create that mode anyway - but pretty clearly in bad taste in our current environments.