Conversation
5090a10 to
b2eb1ee
Compare
Allow the compiler to derive implicit arguments from functions that themselves have implicit parameters (e.g. `compare` for `[Nat]` from `Array.compare<Nat>` + `Nat.compare`). Works transitively and is depth-limited via `--implicit-derivation-depth`. Structural derivation (`__record`/`__tuple` combiners) will follow in a separate PR. Made-with: Cursor
b2eb1ee to
7d14732
Compare
6afe071 to
13fddcf
Compare
crusso
left a comment
There was a problem hiding this comment.
Some interim comments before GH loses them...
| let experimental_stable_memory = ref experimental_stable_memory_default | ||
| let typechecker_combine_srcs = ref false (* useful for the language server *) | ||
| let blob_import_placeholders = ref false (* when enabled, blob:file imports resolve as empty blobs *) | ||
| let implicit_derivation_depth = ref 100 |
There was a problem hiding this comment.
I guess this will affect all imported code too. Maybe that's ok but seems fragile.
There was a problem hiding this comment.
yes, but we don't have flags per file/package and we need some default cap
| Shared and Composite functions (actors, async) are excluded | ||
| since implicits are a local-scope, synchronous mechanism. *) | ||
| let is_matching_typ_with_holes hole candidate_typ = | ||
| match T.promote hole.hole_typ, T.promote candidate_typ with |
There was a problem hiding this comment.
match T.promote hole.hole_typ, T.promote candidate_typ with
I'm not sure you should promote the type of the hole, only the candidate. If this was a variable, it would replace it by its bounds (in what I think is a negative position). Suppose we had a hole of type T <: U and a candidate of type U. Then U <: T only if we substitute U for T. But we could actually substitute any W < U (strictly less than) for T too.
There was a problem hiding this comment.
I'll see if I can construct a counter-example...
... Hmm, not having much luck.
| since implicits are a local-scope, synchronous mechanism. *) | ||
| let is_matching_typ_with_holes hole candidate_typ = | ||
| match T.promote hole.hole_typ, T.promote candidate_typ with | ||
| | T.Func (T.Local, T.Returns, [], req_args, req_rets), |
There was a problem hiding this comment.
Is there any reason the hole type can't be a generic function - this seems to require the hole to be non-generic and I guess the witness to be monomorphic? I guess we could extend later if need.
There was a problem hiding this comment.
I guess it's just because we don't support inference for unannotated generic lambdas, right?
There was a problem hiding this comment.
Indeed, this requires the hole to have a function type, though we don't require this elsewhere. That's what tripped me up in #6054.
I guess this approach of inserting implicit arguments is needed to make the existing (flattened) Array.compare work without changes to core.
Another option might be to look for curried applications that produce the desired hole type directly, and then extend, e.g., core Array with:
// existing Array.compare
public func compare<T>(a1 : [T], a2 : [T], compare : (T, T) -> Order) : Order { ... };
// derivation
oublic func deriveCompare<A>(compareA : (implicit : (compare : T -> T -> Order))) :
(compare : ([T], [T]) -> Order)) = func( a1, a2) { compare( a1, a2, compareA) };
Then our witnesses are not lambdas that insert arguments but partial applications of functions that only take implicit arguments and produce a matching witness (without constraining the dictionary type to be a function),
Then the search for derivations only considers functions with (exlusively) implicit arguments that produces
a matching type.
But maybe our bimatch is not up to that or this winds up requiring backtracking.
It also means we would need to add some bindings to core to derive compare, eq and toText for generic containers, instead of it just working with our existing core.
There was a problem hiding this comment.
Is there any reason the hole type can't be a generic function
No, just simplicity, we can extend if we ever need that
There was a problem hiding this comment.
I guess this approach of inserting implicit arguments is needed to make the existing (flattened) Array.compare work without changes to core.
Not only that, see #5966 (comment)
|
|
||
| ### Implicit derivation | ||
|
|
||
| When no direct match exists, the compiler can **derive** an implicit argument from a function that itself has implicit parameters. This eliminates the need for boilerplate wrapper functions. The candidate function can be polymorphic (the compiler infers the type instantiation) or monomorphic. |
There was a problem hiding this comment.
I don't think anything here clearly states that the hole must have a (monomorphic) function type although perhaps its implied by saying that stripping the implicit arguments from the candidate produces a matching type.
I now realize that's probably why my example in #6054 fails.
There was a problem hiding this comment.
I've explored this extension in #5991 (see comments there)
but this approach of deriving values (records/modules with funcs) instead of funcs is weaker, as it does not extend to __record to allow generic folds over records/tuples/etc
We could have both but this complicates the PR even more
So I'd just refactor your example to have two separate implicits, instead of a single record that ships both
This approach is simpler and extends to __record.
I'd just define toCandid and fromCandid separately instead of rep.
btw, if you want to allow context-dot syntax like myObj.toCandid() then you'd need an indirection like
func toCandid<A>(self : A, toCandidImpl : (implicit : A -> Blob)) : Blob {
toCandidImpl(self)
}| 31 │ ignore needsCompare([1], [2]); // Array.compare is unique head, but inner Nat compare is ambiguous | ||
| │ ^^^^^^^^^^^^^^^^^^^^^^ | ||
| = note: Implicit derivation failed: | ||
| `compare : (Nat, Nat) -> Order` ambiguous: `MyNat.compare`, `Nat.compare` |
There was a problem hiding this comment.
| `compare : (Nat, Nat) -> Order` ambiguous: `MyNat.compare`, `Nat.compare` | |
| `compare : (Nat, Nat) -> Order` has ambiguous solutions: `MyNat.compare`, `Nat.compare` |
Might be clearer.
| Format.asprintf env "`%s : %a` not found" | ||
| name display_typ_oneline typ :: acc) | ||
| | HoleAmbiguous ambiguous_candidates -> | ||
| Format.asprintf env "`%s : %a` ambiguous: %s" |
There was a problem hiding this comment.
| Format.asprintf env "`%s : %a` ambiguous: %s" | |
| Format.asprintf env "`%s : %a` has ambiguous solutions: %s" |
Summary
Part 1 of #5903 — implicit argument derivation only, without structural derivation (
__record/__tuplecombiners).The compiler can now derive implicit arguments from functions that themselves have implicit parameters. For example, an implicit
compare : ([Nat], [Nat]) -> Orderis automatically derived fromArray.compare<Nat>whenNat.compareis in scope.comparefor[[Nat]]chains throughArray.compare<[Nat]>→Array.compare<Nat>→Nat.compare)rec_bindings--implicit-derivation-depth(default 100)resolve_holeanddisambiguate_resolutionsto support the new derivation tiers while preserving existing direct-match behaviorStructural derivation (
__record/__tuplecombiners for records and tuples) will follow in a separate PR on top of this one.