Pre-SIP: warn against unidiomatic features (e.g., null value and unsafe casts)

As a person who makes a living writing both Scala and Java code I have to disagree with this proposal. A selling point of Scala is its Java interop, and requiring me to use an import just to pass null or check a method result for null is counter to Java interop and would discourage me from using Scala in the future. If I’m not allowed to write null, I’m going to end up writingNone.orNull, which is in no way better.

I like the proposal, but understand the sentiment. Perhaps a compiler flag to disable all such checks would work for you?

Reminds me of the Go joke “you can use the whole language!”, though Go has nulls too so I have to disagree a bit.

An alternative is to have a pure mode for Scala. If you want to turn off core language features, turn it on, and scalac will disallow null, var, while, and so on.

asInstanceOf, @unchecked and @uncheckedVariance I don’t have much of an opinion on.

What about the current language imports, which already hide core language features behind language imports? Would you propose to remove them?

That’s a separate discussion. I’m specifically against requiring an import for null.

I agree with you.
I just do not understand why null can be an unidiomatic feature.

So there is no alternative to null

var a:String = _  // it is good 
a = null   // is it  really unidiomatic? 

Note that this is similar to asInstanceOf on Scala.js. The question comes down to style, specifically of how you interact with libraries defined in another language.

The proposal, if accepted, would strongly favor a style of “wrapping” the external language. I think the only real difference is that the Scala.js community has, from the beginning, strongly encouraged the creation of Scala facades around JS libraries. Hence, it’s an easier sell to make asInstanceOf require an import, since the usage is already mainly in the facade files, not in business code.

The difference is that we are much less in the habit (as an overall community) of wrapping Java libraries, even when those libraries’ APIs are poorly suited to Scala. This change would nudge us in the direction of writing such wrappers. I actually think that would be a positive change, but YMMV…

3 Likes

Not entirely – I personally think consistency is important in this matter.

Having language imports for features deemed dangerous or advanced is a single topic.

I would say that “good” is a exaggeration here. I haven’t used that construct once, in six years of full-time Scala, and in practice I’ve rarely seen it used. IIRC, we don’t even teach its existence in any of Artima’s Scala courses. So I don’t think it’s “good” – IMO, it’s a very-rarely-needed evil, that exists for some edge cases.

Also, I’d be careful not to misinterpret @sjrd’s words. As I understand it, he’s arguing against conflating Option and null too closely. But the fact is, null is often used to mean “optional value” in idiomatic Java code – that’s why it is so common (and idiomatic) to say:

val f: Option[Foo] = Option(javaFuncThatReturnsFooOrNull())

I’m pretty strongly of the opinion (and I don’t think it’s unusual) that null should not usually be allowed to leak into Scala code. It’s occasionally necessary, but in most cases I’ve encountered, unless you specifically need to leave the nulls for performance, they should be converted to and from Option at the Scala boundary.

Hence, I’m in favor of the proposal personally – if a null ever shows up in my company’s code, I want that to be an error…

2 Likes

The null value directly leads to unsoundness in Scala and Dotty.

4 Likes

null is unidiomatic because it’s widely considered a code smell to make an API that relies on it in Scala. Look at the Scala standard library. It’s mostly defined as if null did not exist. You don’t have functions producing null or expecting null (beside Option’s obvious interop functions). Compare that to Java or Kotlin, where it is a natural and widely used feature of the language. In Kotlin they get away with it by checking it statically. In Java it is not checked and is one of the leading causes of bugs in the wild. In Scala it is not checked (yet), so it should really be avoided (lest we get the kind of pervasive runtime errors Java is plagued with).

Of course there are – thankfully! We tend to prefer options. You can even use a library for unboxed options if you want to avoid the allocation.

1 Like

We tend to prefer value classes for that purpose.

class NNumber(val underlying: JBigDecimal) extends AnyVal with NOrdered[JBigDecimal, NNumber] {

  def round(value: NLong): NNumber = {
    if (this.isNull) this else NNumber(this.get.setScale(value.get.intValue(), RoundingMode.HALF_UP))
  }
  ...
}

But I do not agree that option is alternative of null.
I think option is the way to do more save library(with performance loss)
IMHO

I can understand I usually works on low level api. And we can say scala is for high level tasks if you need low level taks use java :wink:
May be such saying is right. I do not argue it.

I understand kotlitn way:

var b: String? = "abc"
b = null // ok
print(b)

I understand java way.

I understand this sip. But I do not like it.
You do not suggest better alternative, you suggest more difficulties.
For example kotlin does not forbid to use null, it suggests other type checking.
IMHO

I get that you need null for your use case – if you’re performance-centric, that’s not terribly unusual. (Personally, I would probably use UOption in that case, but matter of taste.)

But I still think it’s fair to say that null is not idiomatic in Scala. That doesn’t mean illegal – it means “use other tools if you can”. And I think this idiom has changed over the years: I would say that null has become considerably less idiomatic in the ten years that I’ve been working in Scala. Habits have changed in this respect, but the language hasn’t.

The question is, should that idiom be (weakly) enforced at the language level? That’s more subjective – I would say so (possibly with a compiler flag to permit nulls throughout the codebase), but I don’t think it’s a slam-dunk decision. Hence, I think this conversation is useful for consideration…

3 Likes

If I looked at it in this way I would suggest to use following priorities:

  • implicits
  • operator overloading
  • inheritance
  • null

It’s more of a joke.
Although when we talk about safety, I cannot understand why null can be more dangerous than implicits for example.

Implicits/Inheritence are a core feature of Scala. null is not, its only there due to Java interopt.

Honestly if that is your priority list then you should look at language like Go, which basically has the things you are talking about (no operator overloading, no implicits, no inheritance and it even has null!)

I can say more. I have been working with oracle pl sql for a long time. And it is a beautiful language :wink:
I would prefer to use it for some my tasks if it was possible :wink:

But I use scala and I pay for it .
For example if I used kotlin I would be able to do some tasks more easily :wink:
But some tasks would be more difficult for me.
Unfortunately there are no silver bullet.

Seriously.

Go is a beautiful language, but I do not think it is a good argument for that sip.
:slight_smile:

I think it is better to make scala language as generic as possible.

  • reciver function
  • language injection or whitebox macros or preprocessors
  • etc

Instead of culture cultivation which leads to desire to advise using other languages :wink:
IMHO

What’s wrong with the advice to use other languages if they are more suited to the task?

Agreed. Seriously, I think the community has been maturing to the point of recognizing that Scala is not The One True Language For All Purposes. It happens to be my favorite “general heavy-lifting language”, and I’m a huge Scala advocate, but there are plenty of problems for which I’d reach for Rust, Haskell, C#/F# or lots of other possibilities – most popular languages are a good answer for certain problems. (I was quite happy to see the ScalaDays keynote last year being on Rust – it’s healthy to recognize that there are other good languages out there, with lessons for us to learn from.)

Years of experience has taught the community a lot about what works well and what doesn’t in Scala, and the Scala 3 project largely reflects that – it’s adding a bunch of stuff that the consensus says will help, but also removes several things that have proven to be problematic over the years. It’s becoming a little more focused and opinionated, far as I can tell, and I’d say that’s a generally good thing – it’ll help teams work together a little better, and help folks better understand what Scala is good at.

But I don’t think it’s terrible if we acknowledge that it isn’t the perfect solution for some use cases, and that some language features should be used with considerable caution. null is very high on that list, IMO – I have rarely used it in any Scala code except when strictly required by APIs, teach students to never use it except when strictly necessary, and generally consider it to be a ticking timebomb to be avoided. That’s not the only possible attitude, but I think it’s a pretty common one…

3 Likes

I have used it a lot last time I was working in Scala (a 2D game, lot of GUI stuff, it was in every screen class and majority of widgets, so like 2/3 of whole code-base). Mainly in enforcing initialization order in traits with self-type with many dependencies on base class and even other traits base class mixes in.

Simple example demonstrating what I mean which will crash on NPE:

trait B {
  this: A =>
  val b = Seq(2, 3)
  println(a.length)
  println(b.length)
}

class A extends B {
  val a = Seq(1)
  println(a.length)
  println(b.length)
}

Generic solution I adopted was to convert val x = ... to var x: X = _ and add an initialization method to each trait, so base class can initialize everything in a correct order.

It is a good advice.

The definition of more suited is wrong.

I heavely use implicits and more often null when I write domain extention.
But we forbid using implicits and we do not recomend using null in high level business logic. Because usualy implicits in high level cause even more errors than null.

I think logic like :

  • I do not like it,
  • I do not use it.
    lets forbid it if it is posible.

It is probably witch hunt.