-
Notifications
You must be signed in to change notification settings - Fork 325
Add 3.3.0 announcement #1505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 3.3.0 announcement #1505
Changes from 1 commit
d86b291
8e204ef
3b0f4b6
6c6b654
2b98d37
607c458
f8cfeb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
--- | ||
layout: blog-detail | ||
post-type: blog | ||
by: Paweł Marks, VirtusLab | ||
title: Scala 3.3.0 released! | ||
--- | ||
|
||
 | ||
|
||
We are thrilled to announce that after long months of work and careful testing, we have released Scala 3.3.0, the first release in the new 3.3.x LTS series. | ||
|
||
## What does "LTS" mean? | ||
|
||
Scala 3.3.x is the first Long Term Support (LTS) release series. That means that it will be actively maintained for a period of at least three years. We have adopted a release model similar to the one that Java has successfully used for a long time. | ||
|
||
In the following years, there will be new minor releases (3.4, 3.5, and so on) that can bring new backward-compatible features. We are calling them Scala Next; they are equivalent to Java's feature releases. Bug fixes and usability improvements from those releases will be back-ported and released as 3.3.x patches. Apart from those forward and backward-compatible changes, LTS will be feature frozen. | ||
|
||
The LTS model doesn't change anything in our compatibility guarantees. Projects built with all future releases will be able to depend on any library compiled with Scala 3.3.x. This is just our standard guarantee that the newer compiler can always consume the output of the older version. | ||
|
||
LTS might be a great choice for library authors who can now receive constant bug fixes and developer experience improvements without forcing the users of the library to update the compiler version in their project. | ||
|
||
You can read more about our compatibility guarantees in [the recent blog post](https://virtuslab.com/blog/the-scala-3-compatibility-story/). | ||
|
||
## What's new in Scala 3.3.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using the following explicit subsections for this section:
We must make extra clear what is language and what is not language, as it confuses users too often. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should make things clear what is a language feature for sure, but maybe we can have some tags instead? The sections might comprise of a single point, so that seems very formal for the sake of it being formal. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think having a section with a single point is good. It makes it very obvious that there is actually very little that changes in the language (contrary to what we can read left and right). |
||
|
||
### Linting | ||
|
||
Scala 3.3.0 brings back linting to the Scala compiler. Right now, you can enable checking for unused symbols and discarded values. More linting options will come soon in the following Scala 3.3.x releases. | ||
|
||
#### Checking for unused values | ||
|
||
There is an entire family of compiler options added in 3.3.0 for checking and reporting different kinds of unused symbols: | ||
|
||
- `-Wunused:imports` - for unused imports | ||
- `-Wunused:privates` - for unused local definitions | ||
- `-Wunused:locals` - for unused local definitions | ||
- `-Wunused:explicits` - for unused explicit parameters | ||
- `-Wunused:implicits` - for unused implicit parameters (parameters in `using` clauses) | ||
- `-Wunused:params` - for all unused method parameters | ||
- `-Wunused:all` - for enabling all of the above lints | ||
|
||
#### Checking for discarded values | ||
|
||
Discarding non-unit values is usually the symptom of subtle mistakes. The compiler can now warn you about all discarded values saving you from bugs resulting from those hard-to-spot mistakes. | ||
|
||
In the following simplified example | ||
|
||
```Scala | ||
def failure: Either[String, Nothing] = Left("something broke") | ||
|
||
def failedComputation: Either[String, Unit] = | ||
Right(()).map(_ => failure) | ||
``` | ||
|
||
the programmer, by mistake, used the `map` method instead of `flatMap`. The code still compiles, but due to value discarding, behaves unexpectedly, returning `Right(())` from the `failedComputation` method. If the `-Wvalue-discard` flag is enabled, the compiler will report the warning, saving the user from a potential bug. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be possible to show the compiler output in such a case? |
||
|
||
### More consistent braceless syntax | ||
|
||
Braces around method parameters can now be replaced with a colon. This can lead to cleaner, shorter, and often more readable code in places like configuration DSLs or test case definitions. | ||
julienrf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```Scala | ||
def canFail(input: String): Try[List[String]] = Try: | ||
someComputation(input).flatMap: res => | ||
val partial = moreComputation(res) | ||
andEvenMore(partial) | ||
|
||
class TestSuite extends munit.FunSuite: | ||
test("the job doesn't fail"): | ||
val job = canFail("some data") | ||
assert(job.isSuccess) | ||
``` | ||
|
||
### `boundary` and `break` | ||
|
||
Two new methods were added to the standard library: `boundary` and `break`. They are safer and more expressive replacements for non-local returns, which were deprecated recently. | ||
|
||
`break` allows for a type-safe early escape from anywhere inside the block delimited by `boundary` to its end, returning the passed value from the entire block. | ||
|
||
```Scala | ||
import util.boundary, boundary.break | ||
|
||
def sumOfRoots(number: List[Double]): Option[Double] = boundary: | ||
val roots = numbers.map: n => | ||
println(s" * calculating square root for $n*") | ||
if n >= 0 then Math.sqrt(n) else break(None) | ||
Some(roots.sum) | ||
``` | ||
|
||
When you run the above method, you will notice that it returns `None` when any of the input elements is negative. Moreover, thanks to `boundary`/`break`, you can see from the console output that it stops iterating after encountering the first negative element. | ||
|
||
`break` can jump out of the `boundary` in the function deeper on the stack. To make it safe, only functions with a matching `Label` in their using clauses can break. This can be used to create isolated parts of an application with streamlined error handling. | ||
|
||
Library authors can abstract over `break` and `Label`, creating a nice API for error handling or dealing with uncertain data. We can define the following micro-library as a simplified example: | ||
|
||
```Scala | ||
import util.boundary, boundary.{ Label, break } | ||
|
||
type Uncertain[T] = Label[Left[String, Nothing]] ?=> T | ||
|
||
def fail(error: String): Uncertain[Nothing] = break(Left(error)) | ||
``` | ||
|
||
Then it can be used in the following way to create a fragment of a larger application with isolated error handling: | ||
|
||
```Scala | ||
def getUser(id: String): Uncertain[User] = | ||
val user = findUser(id) | ||
if verifyUser(user) then user else fail("Incorrect user") | ||
|
||
def getAddress(user: User): Uncertain[Address] = | ||
findAddress(user) match | ||
case Some(address) if verifyAddress(address) => address | ||
case Some(_) => fail("Incorrect address") | ||
case None => fail("Missing address") | ||
|
||
def getShippingData(id: String): Either[String, (User, Address)] = | ||
boundary: | ||
val user = getUser(id) | ||
val address = getAddress(user) | ||
Right((user, address)) | ||
``` | ||
|
||
### The new default implementation of lazy vals | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should come earlier, as it is the only thing that may potentially have an impact on existing codebases during the upgrade process. Everything else is things you can profit from after you've upgraded. In the Scala.js release notes, we have an explicit "Changes with compatibility concerns" section before a "What's new" section. See for example https://www.scala-js.org/news/2023/01/26/announcing-scalajs-1.13.0/. Consider doing the same here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On one hand it will be useful to have a list of things that might cause issues when updating, but maybe we can add it as a later section and let people know at the start? "For compatibility concerns look to..." This way we can first focus on things that are most interesting to users? Also if lazy vals are an internal thing, so maybe it should on be in the same section? Or have a section with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not every user has the same interests. When I upgrade something, the first thing I want to know is: is there anything I should pay attention to while I upgrade? 90% of the time, that's all I care about in release notes of software I use. The "exciting stuff" is not interesting to me in that scenario. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A clear link and mention should be ok then, no? I think a lot of people might be interested about the "exciting stuff" so it would be good to cater to both demographics. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, sure. At the end of the day, it's a matter of opinion. If you prefer to show the exciting stuff first given those considerations, go for it. ;) |
||
|
||
Last but not least, we have changed the default implementation of lazy vals. The new implementation has better performance and is safer under parallel access. This may result in improvements in the performance of effect systems. | ||
|
||
## Should I update to Scala 3.3.0? | ||
|
||
If you are maintaining a standalone Scala 3 project without external projects depending on it, feel free to switch to Scala 3.3.0 at any moment. You will enjoy all the improvements in the newest version of the compiler. | ||
|
||
If you are a library author following semantic versioning, we advise you to update to Scala 3.3.0 in the next minor release of your project. Users of your library would also need to bump their compiler version to use the newest version of your library. If you have already published a minor version on 3.3.0 but learned about a critical vulnerability in your library, you should release a new patch release for the previous minor version of your library using Scala 3.2.2 (or any other older version of the compiler ). This patch can be consumed by all the users of previous versions of the library, no matter if they have already switched to 3.3.0 or not. | ||
Kordyjan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Known incompatibility: Stability of inline parameters | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is also for the "Changes with compatibility concerns" section. You probably want to make it more explicit that this a bug fix (so OK to do in a minor version), but for which we have already noticed existing problems in the wild, and so we're helping users by proactively giving them more information on how to deal with it. |
||
|
||
In Scala, only stable paths can be used as prefix in path-dependent types. | ||
|
||
```Scala | ||
class Outer: | ||
type Inner | ||
|
||
val a = new Outer | ||
val aInner: a.Inner = ??? // ok | ||
|
||
var b = new Outer | ||
// val bInner: b.Inner = ??? // error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should not comment out these erroneous lines as the purpose of the snippet is to show compilation errors? |
||
|
||
def c = new Outer | ||
// val cInner: c.Inner = ??? // error | ||
|
||
def method(param: Outer): param.Inner = ??? // ok | ||
``` | ||
|
||
There was a bug that resulted in the compiler assuming that all inline parameters are stable references. This could have led to unsound code being accepted by the compiler and potential runtime crashes. | ||
|
||
```Scala | ||
inline def method(inline param: Outer): param.Inner // error in Scala 3.3.0 | ||
``` | ||
|
||
Such pieces of code are now rejected by the compiler. The migration for that change is simple, as it only requires removing the `inline` modifier **from the parameter**. If the path-dependent type was used, it is safe to say that the intended behavior was to inline the method without inlining the parameter. | ||
julienrf marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.