Scala 3 very impressive, It can replace Python i think

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.

2 Likes

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 F# syntax in 60 seconds | F# for fun and profit 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?

10 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!

12 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

8 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.

17 Likes

Of course number of keywords is not everything. But it usually correlates well with number of syntax rules, which correlates in turn with usages per keyword. To get a more accurate picture one also needs to look at the size of the whole context-free grammar. I did not do that for F#, but for many other languages, and found that, again, Scala is at the lower end of the spectrum, smaller than Java, Kotlin and (I would guess) F#, a lot smaller than C++ and C#, about on par with OCaml, larger than Python.

But of course, number of syntax rules isn’t everything either. Maybe we should stop identifying “small” and “simple”, There is probably a correlation between the two, but it does not look very strong.

Also, if I look at @haoyi’s argument, it turns out that plus points of languages sometimes correlate with extra features. Scala improves on F# because it has higher-kinded types, whereas F# is better with computation expressions. Both of these are examples where a language spends syntax footprint in order to make something simpler which would otherwise have to be encoded (sometimes akwardly). So, that’s another argument why perceived simplicity and size do not seem to be strongly correlated. It’s a bit sad for me, since I was operating under the assumption that the language ideal is to be small, orthogonal and very expressive, so that features can be provided by libraries. It seems the world does not agree that this approach leads to simplicity, overall.

Which is not to say that Scala has not made mistakes. Like any other language it has warts and dark corners. We are trying to fix some of these in Scala 3, and that will hopefully be an ongoing effort.

12 Likes

One quick suggestion to make the language easier would be to have some more organization of the methods in the collections library.

E.g., Scala Seq looks to have about 150 methods. It would be nice if it was possible to filter them by what the methods are broadly doing: E.g. filtering the collection, partitioning the collection, applying a function to a subset of the collection etc. Sometimes I find that I have to look through the list of methods trying to find the right one (or maybe grep by what I expect the shape of the return type to be).

But perhaps something like this could be achieved by adding tags to the method descriptions, e.g., something along the lines of #filter, #maplike, #partition, #conversion, and then some tweaks on the webpages to group or filter the methods based on the tags?

Just an idea …

I think it is more about the possible combinations per use-site, and the complexity of determining / remembering what combination goes at which use-site.

1 Like

10 posts were split to a new topic: Meanings of underscore (including wildcard imports)

Few relatively simple equations rule the physics of the universe, is it? Few people think of the universe as simple, most of us regard it as a highly complex thing.

My private rule of thumb when dealing with Scala code is to expect unexpected :-). Though not everyone feels comfortable with this, especially when tasked with fixing or expanding someone’s else codebase on short notice.

Having the freedom to shape new features is a developer’s best friend and greatest foe at the same time. It needs time and trials to do it right. Unlike libraries, in my usual commercial projects with limited resources and timeframes, it tends to leave things half baked and messy at best. Senior devs spend precious time discussing the merits of using feature A vs feature B, whereas junior devs are scared of the code and use to copy-pasting existing formulas.

What is the conclusion? IMHO Scala language, giving so much freedom, should also aim to provide devs with high regularity and certainty at least at the very lowest levels: syntax, core library, core patterns. Every small irregularity and inconsistency in these small, orthogonal and very expressive features disturb much.

That said, I have to say also THANKS for Scala!

Personally I don’t think it should be a goal to ‘please the world’. Languages like JavaScript and PHP are generally seen as bad design, yet have become two of the most popular programming languages in the world. In a similar sense Go is incredibly limited with design decisions that make no sense to me, but still is often praised for its simplicity. Even if a lack of generics leads to incredible verbose boilerplate and complex solutions like code generation for simple problems.

I rather stay with something like Scala, even if that means less chance of a job. Because I believe it can lead to simpler code.

Another reason why I think Scala is perceived as complex is because it is really one of the only languages that tries to unify FP and OOP from the ground up. From the viewpoint of languages like Haskell, ML, etc. the OOP parts seem complex and unnecessary, whereas from the mainstream languages like Java, C#, etc. the FP and type-system seem complex and unnecessary. I don’t think there is much we can do about that except ‘showing the world’ how well this unification can work.

That said I think Scala could be (perceived) simpler. As I said in another thread, less ‘unnecessary’ choice would be great. For example, with the new anonymous givens I think context bounds are unnecessary. They are only slightly shorter, but provide IMO unnecessary choice. Context bounds also have more limitations, so you need to switch to other syntax anyway when your structure changes. Another example would be to unify functions and methods. Since they have gained some capabilities in Scala 3 I don’t see any reason why we would need both. I am not sure how feasible it is to actually do this, but my point is that the syntax now gives quite some freedom to express the exact same thing. See for example this old Stackoverflow question by @lihaoyi Nine ways to define a method in Scala? - Stack Overflow which I think is still partly valid today.

8 Likes

The discussion on underscore was good — so good, I moved it to its own topic: Meanings of underscore (including wildcard imports)

6 Likes

I do not like python if I had choice I would prefer to use scala2 or groovy in many cases.
But when I need scripts in linux I use python because of ecosystem(there are many libraries like iostat)
When I need light web app I use python because it can be deployed in any cloud easily.

So I am not sure that python and scala can be compared against each other.

Scala native could take a bite from cgi web apps for example but when I think about it I don’t understand how I can easily link an application library(working with c is quite harder). So I will prefer python only because of ability to get many libraries which can be added to project very easily.

Ammonite might work well for the use case you’re describing

2 Likes

I don’t think so, at least when I needed to analyze /proc/pid/io I found a python library very quickly.

I am not sure that static language can be good at scripting at all in general. Ammonite is very good around scala. But it will never be a competitor to bash or python, so there always will be a lack of some libraries

Yeah, if the library you need isn’t available, you’re kind of stuck :man_shrugging:

I’ve used Ammonite a couple times, and for most scripting tasks it’s been quite an improvement. Unfortunately, the one place it lacks is a big use case for me (piping data to and from external processes), otherwise it’d probably replace the bulk of my bash scripts.

4 Likes