Skip to content

Name-based implicits in Effekt#1380

Merged
b-studios merged 46 commits intomainfrom
experiment/name-based-implicits
Apr 21, 2026
Merged

Name-based implicits in Effekt#1380
b-studios merged 46 commits intomainfrom
experiment/name-based-implicits

Conversation

@marzipankaiser
Copy link
Copy Markdown
Contributor

@marzipankaiser marzipankaiser commented Apr 15, 2026

This implements implicits similar to

Daan Leijen, Tim Whiting: Syntactic Implicit Parameters with Static Overloading

It also implements their use to get source positions in the code.

Syntax and Intended Behaviour

Putting a ? in front of a value/block parameter makes it potentially implicit. If trailing implicit parameters are missing, an argument is generated as follows (e.g.):

  • value argument called $x: $x
  • value argument called sourcePosition: SourcePosition("somefilename.effekt",10,3,10,6) (corresponding to the source position of the called function, without with argument list)
  • value argument called callId: A statically unique integer for this call (unique within an Effekt compiler run).
  • value argument of type (...) => ... at {...}: instantiated to box {...} with whatever the block argument would be instantiated to.
  • block argument called $foo of type (A){B=>C} => D : { (a: A){b: B => C} => $foo(a){b} } (eta-expanded to allow for this call to also receive implicit arguments)
  • block argument with an interface type, called $x: $x

Implementation strategy

  • In Namer,
    • find the set of all implicit parameters that any of the overloads could require (i.e. for all functions of that name, all implicit parameters) and generate the source for them already.
    • then name-resolve them, but collect errors and just store them (for now)
  • in Typer,
    • instantiate the arguments (i.e., currently, hardcoded copy of the block literal and corresponding annotations, or just copying them), refreshing them and typechecking them against their respective parameters
      • only happens if at this point, no errors were reported in this case (otherwise, aborts)
    • annotate the instantiated arguments
  • in ExplicitCapabilities, actually change the source to pass the annotated arguments explicitly.

TODO and limitations

  • Check if this works for more than the existing examples (find at least the worst bugs) The tests should be testing a good part of possible uses now.
  • This will cause Typer to run indefinitely or stack-overflow if the search is not terminated by a type/name error. We could try to catch those cases somehow, but this is nontrivial (and will reject valid programs).
    • Opinions?
    • The recursion is quite indirect (and not actual recursion) in typer, and (we can check for recursive uses when checking implicit arguments after instantiating them - this is separate now anyway) at a point where we don't necessarily have the concrete types (due to unification), so checking for "decreasing type size" or similar is potentially hard.
    • Now aborts if, at the same name, we have 10 levels where the expected type does not get smaller.
  • Syntax bikeshedding
  • If merging this, we might want to change Add a 'Repr' pass for generating generic representations #1381 / Show instances for ground types #1123 s.t. they provide only the definitions for base and recursive cases (not for all types used etc).

@marzipankaiser marzipankaiser added feature New feature or request area:typer experiment Experimental branch, do not merge! labels Apr 15, 2026
@marzipankaiser marzipankaiser force-pushed the experiment/name-based-implicits branch from 81d111c to 5b301e2 Compare April 15, 2026 17:52
@marzipankaiser marzipankaiser changed the title Daan-style name-based implicits in Effekt Name-based implicits in Effekt Apr 15, 2026
@marzipankaiser marzipankaiser marked this pull request as ready for review April 15, 2026 21:59
@marzipankaiser marzipankaiser added area:parser/lexer and removed experiment Experimental branch, do not merge! labels Apr 15, 2026
Comment thread effekt/shared/src/main/scala/effekt/symbols/symbols.scala Outdated
@marzipankaiser
Copy link
Copy Markdown
Contributor Author

@jiribenes You might be interested in the change from the last commit: I changed it so implicts of boxed type (...) => R at {...} will be instantiated to box {$foo} where $foo is what a block argument of the same name would be instantiated to, so ?compare: (A, A) => Ordering at {} will be filled with box { (arg0: A, arg1: A) => compare(arg0, arg1) }.

Comment on lines +304 to +305
def intArg(v: Long, name: Option[String] = None): source.ValueArg =
ValueArg(name, Literal(v, builtins.TInt, Span.missing), Span.missing)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writing name-resolved source Terms by hand is somewhat annoying.
If we do so more (i.e. if we generate more stuff in source than we do here already), we might want to consider extracting this function, generateResolvedId etc into a somewhat nice library for it. I did not do for the relatively small amount of code here, but decided to at least extract some helpers.

*/
private var nextCallId: Long = 0

private def generateResolvedId(sym: symbols.Symbol)(using Context): (source.IdDef, source.IdRef) = {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Here's generateResolvedId, mentioned in the other comment)

@marzipankaiser marzipankaiser force-pushed the experiment/name-based-implicits branch from 97082df to 85be025 Compare April 20, 2026 14:06
Comment thread effekt/shared/src/main/scala/effekt/Namer.scala Outdated
val interface = recvTpe.asInterfaceType
// filter out operations that do not fit the receiver
val candidates = methods.filter(op => op.interface == interface.typeConstructor)
val candidates = methods.filter( op => op.interface == interface.typeConstructor )
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val candidates = methods.filter( op => op.interface == interface.typeConstructor )
val candidates = methods.filter(op => op.interface == interface.typeConstructor)

Copy link
Copy Markdown
Collaborator

@b-studios b-studios left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow!

@b-studios b-studios merged commit 5eb9423 into main Apr 21, 2026
8 checks passed
@b-studios b-studios deleted the experiment/name-based-implicits branch April 21, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants