Resolving the tension between beginner friendliness and advanced usage

Ideas like this come up regularly. It’s even been baked into the compiler as “language imports”. It’s been tried.

It doesn’t work. If it did work, it would have had an impact a decade ago, and people would not still be complaining about Scala complexity a decade later

Furthermore, consider how many other languages do this sort of thing. Does Python hide metaclasses under a flag? Does Java block usage of java.reflect unless some special configuration is given? C#? Ruby? Kotlin? Swift? Go? No other language chooses to do it this way. I think that’a pretty strong evidence that this is not a promising avenue for exploration.

There is actually one example I can think of. Elm went all in on restricting usage of their internal/advanced/unsafe APIs, in the interest of making people write “safe” code. It just about killed the language and community. Before the lockdown, Elm was thriving. Now Elm is just about dead.

Apart from the poor track record and social proof, are plenty of technical reasons why this doesn’t work. The largest one being that the whole point of these advanced language features is to make code simpler. Nobody adds language features just for you to make a mess. As a result, prohibiting language features just means the user cannot write simple code in the scenarios the feature is useful. Limiting them to be used under a flag means that everyone just googles the error message and adds the flag as a matter of habit. Nowadays, your IDE even auto-adds the necessary language imports for you. And why not? They just get in the way of writing simple code

People don’t write complicated code unnecessarily. These advanced features are used to avoid writing even messier code: possibly using textual source code generation, post-compile bytecode rewriting, unsafe runtime casting or reflection, or compiler plugins or outright forks. We can see this happening in all other languages that do not support advanced features: take away generics, and people are passing around interface{} and casting like they did in Java 4, or string templating using the canadian aboriginal alphabet. Take away macros, and people are doing textual source code generation or AspectJ-style bytecode rewriting. Is that really an improvement?

Consider Scala 2 macros: yes they are a mess. And yes migration to Scala 3 is a pain. Yes there’s plenty of unhappiness. But what would have happened if Scala 2 macros didn’t exist? That would have killed the entire Scala.js and Scala-Native ecosystems (which doesnt support runtime reflection, and thus relies heavily on macros), large chunks of the com.lihaoyi ecosystem (Cask, Mainargs, Mill, Sourcecode, Fastparse, uPickle), SBT 1.0 (which relies heavily on the .value macro), every serialization library out there, and countless other foundational projects in the Scala ecosystem. IMO there’s a pretty good chance that Scala 2 without macros would never have achieved anything close to what it has today

There’s definitely some amount you can guide people i to doing the “right” thing, but in the end you have to trust people that we’re all adults. Other languages get by just fine without artificial barriers to using advanced features, and Scala has had problems despite having these artificial barriers built in. It seems very unlikely that adding more artificial barriers to using advanced features is the solution to any of the problems the Scala language faces today

15 Likes