That is going to cause difficulties for a large portion of the programmers who are not into the pure FP mindset, so I am unlikely to look favorably on such a proposal.
However, it stands a chance, IMO. I am not the only person on the SIP committee, and I can see an argument being made for that variant.
Iâll chime in, the least of the SIP committee members.
I like the idea of what youâre doing, but the syntax / expression still doesnât seem âscalaâ to me. What you want is a way to lift an expression into a context so that youâre operating inside that context, not the current one.
Theoretically, this is a syntax currently best suited for macros (indeed, scala async was one such macro). I think youâll want to do a few things to improve the proposal:
Very rigorous rewrite rules including what contexts/monads you can use. The presence of âifâ is curious here. If we allow pattern matching, what happens then? Iâd like some more detail on that, because I canât line up in my head how this would work.
A clear syntactic-designation that magic is happening (or something different is happening). Right now itâs a bit too invisible to the writer for my taste.
This proposal does not include any rewrite rules behind the conversion of discarded values to unit.
The second code example was about the possibilities of combining that conversion with JVM-level continuations, which will become possible.
It was my tactic error to mention continuations here because all started to discuss this example instead of the very concrete and small origin proposal. In one sentence, it was: âAllow to define a custom operation applied before discarding value or disallow discarding if such conversion not existsâ .
Sorry again for confusion, this would work without any rewriting and interpretation of if and pattern matching will be unchanged. An example was pretended with the phrase âwhen continuations would be availableâ [i.e., on JDK14], so no [this proposal] magic here: Loom does all magic.
The proposal was needed to handle only one problematic case: when Monad with value discarded and we canât insert effect handling there. Ability to define custom operation prevents one of the most commons errors when coding in typescript: forgetting to write async before the call of a function, which returns Promise<Unit>.
I will write a detailed informal post about scala concurrency later in another thread; itâs interesting itself and already implemented without language change, except the case described above.
Returning to âthis PRE-SIPâ: note that problem is not unique for scala, most other languages provide type or function annotations:
Some languages require explicit discard Nim Manual
Anyway, it looks like an idea to allow customizing value discarding will be not supported here. Technically, to exclude problematic case, disallowing implicit value discarding will work too.
So, the next goal - is to disallow discarding values without typed ascription [as implemented by @dwijnan in Scala2 ]
Hypothesis: actually, discarding value is rarely used, and adding type ascription will not be difficult for programmers. I will recheck this by analyzing some corpus of code, somewhat later.
Ah, sorry for my misunderstanding. If the only part of the proposal thatâs relevant is removal of value discarding, or forcing unit conversion (or some other syntax), then I think you should focus the proposal on that.
The motivating example on concurrency is good, but Iâm sure youâd find more examples that could motivate this change. In fact, IIRC weâve discussed the possibility of this in the past.
I think the thing to focus on in your proposal:
Focus the motivation to specifically the thing you want to fix. The example you show has LOTS of things to clean up, but your proposal is for a small piece. Clean that up so that others donât make the same mistake I did.
(minor) Get to the proposed change quickly in the discussion. Possibly put this before the motivation so itâs clear when reading the motivation how the proposed change helps.
Youâll want a good section on migration path for existing Scala users. The analysis you suggest on existing usage of value discarding would be a great start. The big open question on changing something so fundamental in Scala 2 is âhow can we do this without breaking lots of working codeâ. The goal is to not have unwritten code break, and thatâs a good goal. Remember to balance this with the migration costs. It could be your proposal would need a phased approach to account for that.
An interesting avenue of exploration would be to do the following:
Put an implicit conversion from Any to Unit in Predef, and drop the automatic Unit discarding in the typechecker. Does everything still compile? If yes, we have saved a special rule in the type checker, which is always a good thing! If not, we have to figure out how manageable the breakage is.
If this first step is successful, we can then fiddle with where to put this conversion
In Predef, so that it can be unimported. Thatâs the most conservative option. Or:
In the companion object of a trait DiscardedUnit. Inheriting that trait in a type would make the conversion available.
One other note: If we do proper effect checking (which is planned but will still take a number of years to materialize), the need to disable unit discarding diminishes. In such a system writing
def f(): Try[T] = ...
f()
result
would give a âpure expression in statement positionâ warning on f().
I think that would also allow disabling conversion for specific types by introducing an ambiguous conversion. That seems more useful than enabling it for specific types.
Or discarding v means invoking v.discard(implicit d: Discardable[V]).
Enable for all by import Discardable.Any.
@implicitNotFound("${A} is too precious to toss away like pearls before swine")
trait Discardable[A]
@deprecated("Did you intend to fire and forget?")
def forgotten: Discardable[Future[_]] = null
My concern with this approach is this would move from a situation where you can easily enable discard safety on a project level with a simple flag, to one where youâd need to put together an object to contain the exclusion and reference that in the new -Yimports: flag (if Iâm understanding #6764 correctly).
Itâs not that much more work, but it is considerably more arcane. This could be avoided by providing such an object in the standard library, so the flag to enable discard safety would change to something which could be easily shared:
It works for modules but not packages, in scala 2 and 3, it seems.
-Yimports in scala 2 keeps that behavior, where only modules are suppressed. I never saw a justification for the distinction, but I also never heard anyone complain.
For example, this compiles:
import scala.{Int => _, _}
class C {
def x: Double = ???
def y: Int = ???
}
With -Yno-imports, Double is still correctly imported, but Int errors.
@morgan-peschke People have asked for a simple -Ypreamble to prefix every source, so you could write -Ypreamble:scala.Predef.{badconvo => _, _}.
$ scalac -Yimports:java.lang,scala,scala.Predef,mypre.MyPre noadd.scala
noadd.scala:5: error: value + is not a member of C
def f(c: C) = c + " plus text"
^
one error found
They removed various âlanguage-forkingâ options. Maybe a simple flag would fall into that category.
Also, like Blanche DuBois, Scala has always depended on the kindness of strangers to contribute stuff.
Some preliminary results from usage analysis (unfinished), based on playing with dotty-community-build
Near all occurrences of discarded value are:
builder pattern (i.e. StringBuilder.append returns StringBuilder), so, total disabling without exceptions, as in Plan B, will be problematic: afraid, that sb.append("aaaa"):Unit will looks ugly for the big percent of users.
+= (which is strange, according to Scala specifications this should be translated to assignment)
package object statement. (error in defs, should be readed âexcept object statementâ)
Now running next experiment, where total conversion is in predef
Yes. The question is - how describe those cases as universal rules (?). Ie. we can allow this.type; For builders, defined in java code, situation is not clear: StringBuilder.append returns StringBuilder . Also we canât allow any op: X->X, because map.updated is a case, which should be not allowed.
Gotcha, missed that the second part was connected to the first (my fault for originally reading it at 2am). That tracks with my understanding of -Yimports:...
My question was if it makes sense to provide a MyPre equivalent for common cases, to reduce the migration (and explanation) barrier - particularly as explaining why a new Scala programer would want to do this now involves implicit conversions.