Scala 3 very impressive, It can replace Python i think

I read some document for Scala 3, I like it’s changes. Better than Scala2.
I learn Scala 2 for more than 5 years.

I like Python and happy to see Scala 3 more likely Python.
it like feel i just write python code.

And this is very powerful.

If scala can learn more and more from Python, we can see how things change.

The scientist and AI learning , compute, why not using a fast scala , python is good, but too slow.
Scala 3 fast, static typed, and quite similar to python 3.

whoops. great works.

6 Likes

The narrative so far has been that Scala 3 will not be like Python 3, an upgrade that divides the community.

I think the current estimate is that Scala 3 will succeed in this regard.

2 Likes

Coming from using F# daily in my work, would like to thank all devs making Scala 3.
It seems concise with almost every thing I would want in a language.

I have been experimenting with it for bit more than a month. There are few minor nitpicks such as type inference where it seems compiler should be able to infer it though I think this could be improved later, case class naming instead of just record, modules instead of object, I have seen arguments for and against but its not really big of a deal to me.

1 Like

I never found such fears particularly credible, hence I find the achievement a pretty low bar. With the exception of macros, it seems that there’s very little of Scala 2.13 that won’t compile in Scala 3.0. I would be interested to know whether it was typically easier to port Scala 2.9 => 3.0, vs 2.13 => 3.0.

Although Scala 2 macros are going away strenuous efforts are being made to offer alternative mechanisms for nearly all Scala 2 Macro capabilities. The fact that Macros were labeled as experimental ultimately proved irrelevant. It could be argued that the Scala 3.0 is kind of misleading and that the real Scala 3.0 was Scala 2.10 and the coming release should be called 3.4.

Personally I think the major / epic release distinction is unhelpful and a relic of an earlier phase in the evolution of software development. I think we should follow the Java pattern of release names or go to an Ubuntu calendar based release naming scheme.

In fact, at the SIP ski retreat and covid party in Italy at the beginning of last April, they decided to call the Dotty release “Scala X”, but that turned out not to be the road map. I think when MacOS rolled over to 11, that scheme lost its appeal.

Eventually, they decided it makes the most sense to call it Scala 3, because of 3-space indentation. Projects stuck on 2-space tabs can use Scala 2. There is still only one correct way to line up the asterisks in Scaladoc comments.

I was sanguine about the community hanging together until they settled on given ... with.

This will wreak havoc on code bases that switch to 3-char indentation, where you expect everything to line up with your vals and defs.

I still hope they trim object to obj, though my hope may be based on the irrational exuberance of the season.

I have to check whether my SIP for that change is in “pending” state, “under review”, or “ignored”.

That SIP also includes trimming extends to ext. If they also bring back optional extends and accept given ... extends as recently proposed, then we’ll be able to write given ext {}. Probably I’d use that syntax exclusively for extension methods.

The SIP process also includes dispositions “dormant” and “rejected”, which are aliases for “deferred until a student implements it for Dotty.”

Now I have to update my SIP for Scala 3, to trim with to mit so that we can be well on our way to 3-char keywords and 3-space indentation.

It’s generally acknowledged by now that anything not expressed in 3 chars is dubious. So it’s === not ==, for not while, ??? not TODO, etc.

Inexplicably, they recently lengthened Not to NotGiven, but I chalk that up to pandemic fatigue.

It may be some years before “scala” is shortened to “ska”. That might be Scala XXX.

In any case, it’s very exciting to start a New Year and try out a new Scala. That feeling may sustain us for some time.

3 Likes

I fear said mod might be considered one step beyond … i.e. Madness.

1 Like

Remarkably, @propensive was onto something

:joy:

MMS Forth proponents have argued so for decades.

If you coming from F# what do you think of the Scala language? Is it readable, well design, expressive, fine FP? I have heard very harsh criticism that the F# community gives about Scala.

(There’s a thread on that at https://users.scala-lang.org/t/arguments-against-scala-language/7062/7 ; I don’t think we have the same discussion in this thread as well.)

Some of the criticism are just unfounded, TBH. Take that with a grain of salt. The type system in Scala is more rich and powerful and well integrated together, which is what some people complain about, i.e fusion of FP and OOP even in F# community being a non-novel pursuit which I don’t understand sometimes even amongst some folks here. It seems to really work well together than what you’d expect in F#. Maybe its the prior versions they are complaining about I guess.

F# sort of fails when you want to write more generic FP kind of code. You end up having to rely on SRTP, which is sort of poor man’s version of type class. The two mechanisms to do generics (normal .net and SRTP) doesn’t work well with each other very well, its usually the latter that has all the weirdness, oddities and warts, whereas scala really shines in that regard with a more principled approach.

1 Like

F# is really nice and simple language, not as powerful as Scala, but still very well designed. Two things that immediately come to mind that F# does better than Scala:

  • I have to start with syntax. Syntax is the least important aspect of a language, but F# is just so nice. It’s simple, regular, and very light. It has the most crisp syntax I have ever seen, better than Python, Haskell, Scala or whatever else. Scala (and all other languages) should learn from it: https://fsharpforfunandprofit.com/posts/fsharp-in-60-seconds/
  • Computation Expressions. Roughly analogous to Scala’s for comprehensions or Haskell’s do notation, but more generalized and powerful. It cleverly reuses syntactic constructs from regular code (like let, use, for, try, while) and re-purposes them for working on the Computation expression. Scala’s for is a wart, especially compared to this. I hope that in 3.x it take as much inspiration from this as possible: https://en.wikibooks.org/wiki/F_Sharp_Programming/Computation_Expressions

I would also guess that Scala code that uses inheritance instead of composition or functions like Any => Unit (Akka) don’t appeal to people coming over form F#, but that is more of a question of style and the libraries you use.

That’s what I don’t understand. F# has 80 keywords as opposed to Scala’s 40. In what sense is that “simple, regular, and very light.”? This is not a rhetorical question; I’m really interested to find out what exactly is perceived as simple. I have the impression that sometimes the larger a language is the simpler it is perceived; maybe because it means that more things are done in the language itself as opposed to libraries, which can be arbitrarily crazy.

Looking at https://fsharpforfunandprofit.com/posts/fsharp-in-60-seconds/ here’s the syntax for F# lists:

[1;2;3;4]

Two special symbols. Scala uses no syntax at all but relies instead on the general principle of apply methods.

List(1, 2, 3, 4)

Yet F# is perceived to be simpler. Why is that?

7 Likes

Respectfully, Martin, the focus on minimum keywords is actually the opposite of simple for some people.

Consider the underscore in Scala. While it is a single symbol fulfilling lots of roles, it is actually NOT simple for those who have to disambiguate WHICH of its many meanings it could be in the current context. To this beginner, it was overwhelming. I just patiently waited until I had enough reading skill to only have to look it up occasionally.

I would venture to guess that those who find words preferable over symbols find the “:” versus “with” debate a clear win for “with”. And those, much more comfortable flowing with symbols find the “with” meddlesome. What is simple for one is a less simple annoyance for the other.

If we meta up, this is likely the psychology behind all of the bike shedding. Your perspective on simple is very attuned to you, your experiences, and your preferences. Others starting and evolving from other domains like Haskell or F# have different weightings for the same symbolic representations.

Ultimately, you are the final arbiter of what the eventual flow needs to be. And many won’t find it flow-ful until they have been exposed to it for months, if not years. And that’s just how it’s going to be no matter what you end up choosing.

5 Likes

That’s a very good question. I very much appreciate your interest in this and will try to explain as well as I can. Hopefully other people familiar with F# can join in.

In general, I think it’s not as easy to talk about complexity of a syntax. It has a voodoo magic human psychology aspect to it. Number of keywords or rules is precise, scientific and quantifiable. But I suspect it doesn’t entirely capture how the code looks (and is perceived) when printed on screen.

Here are the main reasons why I think some people might prefer F#'s syntax over Scala’s.

Significant white space

F# uses new lines and indentation for blocks.

let simplePatternMatch =
   let x = "a"
   match x with
   | "a" -> printfn "x is a"
   | "b" -> printfn "x is b"
   | _   -> printfn "x is something else"

In Scala 2.x you have to use a lot of { and }.

val simplePatternMatch = {
  val x = "a"
  x match {
    case "a" => println("x is a")
    case "b" => println("x is b")
    case _ => println("x is something else")
  }
}

Scala 3 is changing that, so we’ll see how it will fare in practice :crossed_fingers: :crossed_fingers: :crossed_fingers:

Quiet function application and pipelining

In F# you apply a function to an argument just by putting them next to the other. A legacy of ML, I presume, because Haskell has it too. Another popular way is to use the pipe operator which works on any function – great for modularity.

f x
g (f x)

x |> f |> g
x |> f |> g |> h |> i

In Scala you have to use ( and ) everything. Since |> isn’t in Predef, people would have to (create it or) import it, so they don’t do that. Instead, they rely on method call chaining for similar effect, which is nice, as long as you use the members available on the object. When not, you’d either have to create an extension method, but that’s not worth it for just one or two use cases, so people resort to ( and ).

f(x)
g(f(x))

f(x).g
h(f(x).g).i

Function composition

Since in F#, “everything is a function”, you can use >> to compose two of them.

f >> g

I remember that in Scala I sometimes had trouble with composing functions (>>>?), because not all the things were functions, some were just members/methods and so I had to resort to creating a lambda verbosely instead of the desired f >>> g. But I have trouble finding a specific example, so this is me speculating here.

{ x => g(f(x)) }
f >>> g // sometimes didn't work for me? idk

Perhaps some people more knowledgeable about this could tell us if Dotty improves this.

Currying

Functions are curried by default in F#, that makes their definitions more uniform.

let f x y = ...

In Scala you have to make decisions on how many blocks you want to have and what goes into each.

def f(x, y) = ...
// or
def f(x)(y) = ...

Value declaration

F# has only let, for both functions and “normal values”. There’s also the mutable qualifier, but that is discouraged. It uses module for module declaration (but they’re not worked with as normal values, unlike in Scala).

let name = "hello"
let double x = x + x
let mutable num = 5
module M =
  let x = 3

Scala has val, var (discouraged), def, object and val can also have the lazy qualifier. The distinction between zero argument def f = ??? and val f = ??? might feel subtle to some people.

val name = "hello"
def double(x: Int) = x + x
var num = 5
object M {
  val x = 3
}

I could see why somebody might feel F# is simpler in this regard. (But it has to be said that it has easier time achieving that because it doesn’t have all the functionality Scala does.)

Type declaration

F# has only the type keyword for ADTs, records, type aliases, classes, interfaces. For abstract classes, it uses the [<AbstractClass>] annotation.

type Tree<'a> = 
  | E 
  | T of l: Tree<'a> * v: 'a * r: Tree<'a>

type Person = { First: string; Last: string }

type Transform<'a> = 'a -> 'a

type Product (code:string, price:float) = 
  let isFree = price = 0.0 
  member this.Tag(currency: string) = sprintf "%s: %f %s" code price currency

type IPrintable =
   abstract member Print : unit -> unit

Scala has class (optionally with abstract or case modifier), trait, sealed modifier and type for type aliases/members.

sealed trait Tree[A] extends Product with Serializable
case object E extends Tree[Nothing]
case class T[A](l: Tree[A], v: A, r: Tree[A]) extends Tree[A]

case class Person(first: String, last: String)

type Transform[A] = A => A

class Product(code: String, price: Float) {
  val isFree = price == 0.0
  def tag(currency: String) = s"$code: $price $currency"
}

trait Printable {
  def print(): Unit
}

(Scala 3 is fixing the ADT situation :tada:)
For both let and type, it’s always followed by = and a possibly newline and indentation. No with, :, .., where or anything like that. In a different world, I could imagine Scala adopting something like that

class Product(code: String, price: Float) =
  val isFree = price == 0.0
  def tag(currency: String) = s"$code: $price $currency"

In F#, it’s noteworthy that since the keyword is (almost) always just type, the meaning of it is determined by what follows after the =. This is analogous to how let works.

Code is not cyclic, has to be sequential

This isn’t at all related to syntax, but it’s an awesome, sometimes under-appreciated feature of F# that I sorely miss in Scala.
Modules and the code in modules can’t be cyclic, the compiler requires that it is sequential, from top to bottom, code files processed linearly from one to the other. There are escape hatches, because there are legitimate reasons to break these rules, but because the enforced default is no cycles, it leads to more clean and readable F# codebases.

module A =
  let double x = x + x
  let f x = double x + 42
module B =
  let triple x = A.double x + x

Where as in Scala you can write

object B {
  def triple(x: Int) = A.double(x) + x
}
object A {
  def f(x: Int) = double(x) + 42
  def double(x: Int) = x + x
}

Avoiding cycles is a great way to simplicity.


There’s an interesting story to F#'s syntax. A bit over 10 years ago, it underwent a similar, also elaborate, simplification and white-spacification as Scala is undergoing now. It started with “verbose” syntax, but transitioned to the “light” syntax which requires less keywords.

More on that story in the article The Early History of F#, chapter 9.10 F# 1.0 - Improving the Functional Core: Indentation-Aware Syntax (on page 36) and the blog post it links to.

Btw, with your example [1;2;3;4] vs List(1, 2, 3, 4) I agree, after getting used to it, I now prefer it the List(...) way. If only I had to be a devils advocate and tried to find reasons why is [...] better, I’d say that List could possibly be any method or object with apply method, depending on your imports and so you have to be paranoid, while with [...] you can always be sure that it’s just a list and nothing else.

For the record, I’m not just some Scala hater. Here I’m appreciating Scala’s strengths in another forum. I’m very grateful that you Martin (and all the people who helped you) started working on Scala all those years ago, so that I today could write Scala, have fun and get paid for it. :bowing_man: Keep up the awesome work!

9 Likes

What people perceive as simple:

(1) Regularity: rules are few and simple
(2) Distinctness: distinct features look different
(3) Familiarity: features coincide with other popular languages

For example, brace-delimited blocks are simple, because:

(1) Regularity: It’s one simple rule: { starts a block and } ends it
(2) Distinctness: braces are only used as block delimiters and nothing else
(3) Familiarity: every popular language except Python uses braces

6 Likes

Well-written, except that I think files ordering is not a feature (it is controversial among some F# users), even though it is touted as such by evangelists. It has been one of the massive source of pain for me,

  1. It forces you to write types in one file and it’s behaviour in the other,
  2. You rely on the editor to order the files.

I do agree on other points you mentioned though.

I don’t know how useful a metric number of keywords is as an indicator of simplicity from a user’s point of view. An extreme counter-example is the x86 instruction set. I have no idea how many instructions there are in it these days but it’s been shown that you could reduce the entire set down to a single instruction (MOV or XOR both work), and still have full functionality and turing-completeness. Now obviously no one would find coding with a single instruction simpler, it’d be a nightmare for a human to work with but if you were responsible for writing a x86 VM or emulator, you’d definitely find it simpler to implement.

That extreme example feels pretty analogous. Instruction/keyword counting will definitely impact ease/difficulty of use but not in a way that lower count always means easier to learn or use.

I wonder how reliable a metric it would be if, in addition to counting keywords, one also counted usages/meanings/semantics per keyword. In the instruction set example you end up with just one instruction but it now correlates to many, many user intentions. If we imagined that F#'s 80 keywords were all beautifully unique and unambiguous, and within Scala’s 40, let’s imagine that 30 of them have multiple purposes that require users to consider the usage context, well that might provide an explanation.

4 Likes

As someone who came to Scala from FSharp, I must say that I found FSharp syntax awkward and clunky despite the “light” indent-delimited syntax.

Martin’s point about the number of syntactic constructs resonates with me. Many things that are special syntax in F# are just function calls in Scala, and even though I haven’t written F# in years I can off the top of my head pull out a half dozen examples:

F# Scala
[1..100] Range(1, 100)
[2;3;4;5] List(2, 3, 4, 5)
{First="John";Last="Doe"} Employee(first = "John", last = "Doe")
[|"a"; "b"|] Array("a", "b")
array1.[0] array1(0)
{ paul with Name = "Jim" } paul.copy(name = "Jim")
fixture :?> EdgeShape fixture.asInstanceOf[EdgeShape]
fixture :? EdgeShape fixture.isInstanceOf[EdgeShape]
fixture :> EdgeShape fixture: EdgeShape

Number of keywords isn’t everything, as @japgolly correctly points out, but neither is it nothing, and coming from F# to Scala I definitely noticed how regular everything felt.

That’s not to say F# doesn’t do anything right: computation expressions are superior to their Scala counterparts, parametrized active patterns are better than Scala extractors, their use of thin-arrows rather than fat-arrows makes things more consistent, etc… But they also got lots of things “wrong” as well: the lack of higher-kinded types makes collection transformations repetitive and verbose, the lack of co/contra-variance in a language with subtyping results in boilerplate casts, the disparate naming conventions between F# and the host C# language results in weird looking code, and the FP/OO schism is much more visible than in Scala.

We should definitely take the parts of F# that are nice and would fit well in Scala, but we should beware of grass-is-greener first impressions that do not accurately reflect the trials and tribulations of using a language seriously in production.

15 Likes