unisonweb / unison

A friendly programming language from the future
https://unison-lang.org
Other
5.82k stars 272 forks source link

Make TDNR more predictable #448

Open pchiusano opened 5 years ago

pchiusano commented 5 years ago

I'd like TDNR to be more predictable. Lately I've been avoiding it since it gives odd results unless the rest of the code also typechecks (often not the case).

Something that might be relevant is that the way we currently select possibilities is by putting a blank in the program and then running typechecking to learn the type of that blank. If the program has type errors elsewhere, it's possible that inserting that blank may cause the program to typecheck, but with a weird type for the blank.

I'd probably start by collecting a number of examples of behavior we don't like for TDNR, then see if we can come up with something that's highly predictable.

Or it might be that TDNR is fine and just needs better docs and a few bugfixes.

Related issues:

377 #376 #370 #366 #845

pchiusano commented 5 years ago

Some options on the table:

  1. rethink it, come up with a more predictable algorithm
  2. disable it entirely
  3. leave as is, with disclaimers
francisdb commented 5 years ago

https://acronyms.thefreedictionary.com/TDNR ?

aryairani commented 5 years ago

@francisdb IMO, The Dark Night Rises is predictable enough; the one we want to improve is Type-Directed Name Resolution.

pchiusano commented 5 years ago

Current algorithm for TDNR:

  1. For each TDNR slot, replace it with a blank, which is a fresh existential, then run typechecking and learn the type of that blank. Based on that type, substitute in the unique matching term, if that exists, otherwise it's a type error.
  2. Repeat for the next TDNR slot, until all TDNR slots have been filled. Note that we do N typechecking passes, one for each TNDR slot.

In a program with type errors elsewhere and multiple TNDR slots, sticking a blank with a fresh existential type for each slot is way underconstrained and generates unpredictable results. The blanks can cover up the type errors and cause them to resurface elsewhere (or prevent TDNR elsewhere), and when multiple TNDR slots interact (think foo (x + y), it's also unpredictable).

Alternate algorithm

Here's a single pass algorithm that I think would be predictable:

  1. To synthesize a function application, f x y z, where f is a TDNR slot, we first synthesize x, y, and z. Then we see if any of the possibilities for f can be applied to these types. If yes, and it's unique, do it. If no, type error.
  2. To check a TDNR slot, f, against a type, t, we do the same - see if any of the possibilities for f are a subtype of t and if it's got a unique solution, use it.

Notice both rules are totally local and can be done in a single typechecking pass. So long as the argument types are known at the point of the call, that type information will be used to select among the functions f. A typical usage is like:

foo : Nat -> Nat -> Nat
foo x y =
  x + y

Here, the synthesized types of x and y (based on the type signature) will be Nat, so the right + is selected. Another possibility is like x + 1, even when x is of an unknown type, since the 1 literal has type Nat and that narrows it down to Nat.+.

Other examples:

Some drawbacks:

It reminds me a little bit of multimethods - dispatch is done based on the types of the arguments. Though the Rule 2 makes it a bit more flexible.

pchiusano commented 5 years ago

Ideas on implementation: