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

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.

I don’t understand what you mean by that. Where is this definition of more suited coming from?

Lets forbid it if it is posible.

Let’s get back down to earth. This thread is a pre-SIP about giving a warning when a language import is not present, like what happens now for existentials or implicit conversions. Not about forbidding anything.

1 Like

It is wrong assertion for me.

Ok.

Your example seems like a legitimate use case for (the now deprecated) DelayedInit or the hypothetical OnCreate. You could add your use case to the discussion there.

1 Like

Just some examples of base libraries on java with heavy usage of null

I do not want to make additional works in such cases. And the arguments like:

  • null is unidiomatic because it’s widely considered a code smell to make an API that relies on it in Scala.

Don’t convince me

I am sure that people which makes api are reasonable enough to understand when they should use it.
The error of that sip that it suggests difficulties for library users. If it were for library authors I would be able to think it is good.

You can use lazy val to avoid this problem completely. Its actually recommended to use lazy val if you use cake pattern/trait mixin which you appear to be dong.

There is some performance overhead on lay val though, so I guess it depends on how much this effects your game.

I quickly reviewed that API, and the only requirement for null there seems to be in the constructor, which is easily replaced (instead of new File(null, child) simply use new File(child))

What use case do you see in this API where you want to use null that I’m missing?

Yes because there isn’t an alternative in Java up until Java 8 (which introduced option types but they are very hard to use)

Well you should because its not considered idiomatic. There are already plenty of tools in Scala to interact with Java if you happen to use Java libraries (i.e. the Option constructor which returns None if the value is an option. There is also scala.util.Try which catches thrown NullPointerExceptions)

Sure, but using null is not considered idiomatic in Scala code, that is a fact. There are exceptions (i.e. performance, Java interopt) but they are just that, exceptions.

Are you joking?
Instead of writing:

 val a = file.getParent()
 if (a!=null){
 .... 
 }

I must use plenty of tools in Scala?

Thank you. I hope I will never have to use it.
:slight_smile: