openlang

implicit vs given in a half-migrated Scala 3 file

Honestly, keeping implicit in Scala 3 was the right call and it still annoys me every week.

Right call because nobody had to rewrite a codebase on day one. Old-style implicits still compile, Scala 3 can use libraries built for 2.13, and you move to given and using one file at a time.

Annoying because the half-migrated file is the hardest Scala to read. One instance comes from an implicit val, the next from a given, and you need both rule sets in your head to find where a value came from. The new rules are better on their own. A wildcard import like import foo.* no longer pulls in givens, you write import foo.given and the import says what it brings. Implicit conversions go through Conversion now, and using one without importing scala.language.implicitConversions gets you a feature warning.

So yes, compatibility won. The price is a long stretch where readers carry two mental models, and I haven't seen a date for the old one to go.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    I only know this from Kotlin, which made the same bargain with Java, and there the bill arrives as platform types. A String coming back from a Java method shows up as String! in error messages, which is the compiler's polite way of saying it has no idea. Treat it as non-null and the check moves to run time. If you're lucky that's a tidy NullPointerException right at the assignment, if you aren't it's three calls later. Annotate the Java side for nullability and Kotlin believes you. Which is fair. Still two mental models, but at least the compiler admits there are two.

  2. 固定链接

    Keeping the old form compiling is fine as long as a newcomer meets the new rules first. If new files start out with given and using, and the old style only survives where nobody has migrated yet, the first thing a beginner copies is the new form and the old one reads as legacy.

  3. 固定链接

    Part of the two-models feeling is smaller than it looks. Resolution is one pool. An implicit val can fill a using parameter, and summon finds it just as it finds a given, so the search itself doesn't care which spelling defined the value.

    Where the half-migrated file really bites is imports. import foo.* still brings in the old-style implicits, because they're ordinary members, but it skips the givens. Move one instance in foo from implicit val to given, leave the caller alone, and the caller stops finding it. That at least fails to compile. Worse is when it picks up a different candidate from somewhere else in scope and everything compiles. The fix is import foo.{*, given}, and that import line is the first thing I check when a value seems to come from nowhere.

同一个问题的其他角度

  1. My function returns an array when all I asked for was a path

    Writing a PowerShell function from scratch for once (rather than squinting at someone else's), and it misbehaves in a way I c…

    回复 4最近活动 powershell
  2. My init set fill-column for exactly one buffer

    Had (setq fill-column 72) in my init for ages and only just noticed that M-q in a fresh buffer still wraps at 70. (In *scratc…

    回复 8最近活动 emacs-lisp
  3. Upgradeable means the storage layout is forever

    People hear proxy pattern and think the immutability problem went away. It moved. The proxy holds the state and forwards call…

    回复 3最近活动 solidity