Hacker News new | ask | show | jobs
by tastyminerals2 2050 days ago
Scala is weird. Sometimes it feels fluid and effortless but sometimes it simply cannot do things you would expect it to. For example, chaining custom methods. Why is it possible to do "arr.map(_.nonEmpty).filter(a => a % 2)" but not possible "arr.map(_.nonEmpty).myCustomFilter(_)"? In D thanks to UFCS and Properties you can chain all kinds of std and custom stuff together which looks more "efforless" and FP in my eyes.

Syntax aside I find Scala code hard to read due to how much implicitness it allows which stems from its "do FP or die" attitude which is again, weird for a language designed to incorporate both OOP and FP. Should I mention that in this regard D does a better job at serving the both worlds?

And finally, which threw me off completely is a real world case where we had to implement a word-entropy based algorithm to process tons of textual data and it appeared to be not much faster than a Python version despite all the optimization we tried to do. Scala is fast but there are many faster languages including Java itself. Considering the fact that its compiling times force you to leave your seat for a cup of coffee, you'd rather think of investing into another hard FP friendly language like Rust which simply has a wider applicability area. But yeah, this is all speculative but if you are thinking about what next language to pick up, these things start to matter.

Where Scala fits nicely is a small team which needs to build some business logic on top of the existing Java backend stack quickly and have a reliable working system. This is where it really shines. Scala provides a nice layer for JVM devs who have a soft spot for FP, safe and elegant solutions.

4 comments

There are no "special" methods in Scala. If you see something like "arr.map(_.nonEmpty).filter(a => a % 2)" in the standard library, you can use that syntax yourself too.

E.g. "arr.customlyFiltered()" is easy to do. Or even easier "arr.filter(myCustomFilter)". If something doesn't work out for you, please feel free to use e.g. scalafiddle.io and make it example, then you will be helped! :)

Scala is indeed not a high performant language. It is as fast as Java and compiles as fast as Java if you only use Java's features. However, then why use Scala in the beginning?

Scala is great for writing very maintainable and reusable code, especially with a big team and a lot of business logic. It is also great to "glue" things together like you can do it with Python. For these things it has more than sufficient performance in my opinion.

There is no straighforward elegant way of doing "obj.scalaMethod.myCustomMethod.scalaMethod.myCustomMethod" in Scala. I want to create a method and just inject it into the chain, no fiddling or ducktaping or using some black Scala magic.

"def myFun(a: String, b: Double): Boolean = {...}" and then "obj.mapValues.myFun.forall(_)" or just anything similar.

And yes, Scala is exactly the language when you need safe and maintainable code with far less unit tests required than in Java. But you have to invest into it and sometimes it is simply not worth it. The Scala2 to Scala3 migration situation makes things even worse unfortunately.

There is a feature in Scala 2 that covers the specific thing you want: implicit classes. You can use them to add custom methods. It's clean and straightforward.

And it will be even easier in Scala 3, where this pattern is coded into an even simpler feature: extensions.

In the Scala 2.x this can be done with an implicit class.

It's a bit cumbersome and not very use-case oriented (mechanism over intention) and so Scala 3 has introduced extension methods[0].

This gives you exactly what you want.

Since Scala 3 is pretty much completely backwards compatible (and with the use of Tasty you can use Scala 3 code in Scala 2.x projects), so I don't see the migration as a big problem. The Scala team has spent a lot of work to make the transition as painless as possible.

[0] https://dotty.epfl.ch/docs/reference/contextual/extension-me...

Thank you for URL. Just when I needed. Any idea when "Scala 3" can be production ready ?
From what I saw in the latest presentation by Martin, we can expect Scala 3 to be released in late Spring/early Summer 2021.

Part of the release testing is building the community release[0], which is a large part of the relevant Scala F/OSS code out there.

Whether you consider that to be production ready you have to decide for yourself, I myself am pretty happy about it.

Keep in mind that for now Scala 2 style code will still be compiled and you can use existing Scala 2 code in your Scala 3 project. The migration should be pretty smooth.

[0] https://github.com/scala/community-build

It's not really black magic https://docs.scala-lang.org/overviews/core/implicit-classes.... but I don't really see why having a filter as a method should even be encouraged.
Implicits are not encouraged in our code base and for a good reason.

This was just an example. It's not about filter at all but the general method chaining.

Here is example from D if I must:

    real[] fun(int[] arr) {
        return arr.map!(a => a.to!double / PI).array;   
    }

    void main()
    {
        int[] arr = 100.iota.array; // [0, 1, 2, 3, ...]
        real[] newArr = arr.map!(a=> a*2).array.fun); // [0, 0.63662, 1.27324, ...]
    }
I don't know how you can chain a custom method "fun" to the output of the "map" in Scala without duck typing. Why is this not possible when all conditions type-wise are met? Why you can chain std methods like map, filter, reduce, fold etc. but not custom ones?
Ah, I understand what you mean now. You are looking indeed for the thrush operator. I think it should be built into Scala's standard library, but until that happens, you can use the mouse library or build it yourself. Here is an example:

    // Need to define this once somewhere in your project
    implicit class TrushExtension[A](anything: A) {
      def |>[B](function: A => B) = function(anything)
    }
    
    
    // Your application code
    def fun(arr: Iterable[Int]) = arr.map(_.toDouble / Math.PI)
    
    val arr = 0 to 100
    val newArr = arr.map(_*2) |> fun
    
    newArr.foreach(println)  // prints 0, 0.63662, 1.27324, ...
Execute or change the code here: https://scalafiddle.io/sf/WAKhZtJ/0

This is maybe not exactly as convenient, but it comes pretty close.

"someA.someB.someC" becomes "someA |> someB |> someC".

If you would call it pipe operator, like it's called in many (Elm, Elixir, F#, OCaml) other languages that have it, people would understand you faster IMHO. It is even discussed for inclusion into JavaScript.
Looks good! Thanks for the tip.
Scala is more strict than D for this use case. In Scala, if you want a function to be available to a certain type as if it was a method call, you need to be explicit about it. You have to declare an "implicit class" that takes the base type as an argument, and define the function as a method of the implicit class. You also need to ensure the implicit class is in scope. Once these conditions are met, you can use it as a method.

    val t1 = MyType()
    def fun(t: MyType, argument: Int) = argument

    // can't do this yet
    // t1.fun(42)
    
    // In Scala 2 you use an implicit class to add methods to a type
    implicit class MyEnrichedType(t: MyType) {
      def fun(argument: Int) = fun(t, argument)
    }

    // In Scala 3 you use an extension
    extension (t: MyType)
      def fun(argument: Int) = fun(t, argument)
    
    // now it can be done
    t.fun(42)
Regarding your question about the stdlib methods being able to be chained: they are not special. They are defined for the type the methods return, so they can be used.

Rejecting all kinds of implicits and then complaining about Scala missing features is a bit unfair. "Implicit"is a single keyword, but not a single feature. Implicit arguments, implicit conversions and implicit classes are not the same thing. Fortunately, Scala 3 will clear this misunderstanding.

Assuming that your object is a list of tuples, then you can do:

    val myFun = (a: String, b: Int) => b % 2 == 0
    List("a" -> 1, "b" -> 2, "c" -> 3).map(myFun.tupled).forall(identity) // false
    List("a" -> 2, "b" -> 4, "c" -> 6).map(myFun.tupled).forall(identity) // true
But I think that's not what you mean... are you maybe looking for the "thrush" / |> operator? Or do you have some example from another language that does a better job and show how it looks there?
> you'd rather think of investing into another hard FP friendly language like Rust which simply has a wider applicability area.

FYI Aaron Turon did their PhD on Concurrency+Scala, and ended up leading the Rust core team for more than enough years.

> Rust which simply has a wider applicability area

Rust is a great language but that's a pretty insane statement. The ecosystem is still tiny compared to the JVM.

Rust is a great language, and Scala is a great language, but they are different. They optimize for a different thing. Rust definitely rocks at high-performance, resource management, fine grained control over all the aspects of the program, at the expense of developer's time. Scala rocks at developer productivity and building abstractions, sacrificing a bit of performance.

However, I must say that Rust is also a very productive and quite powerful language (albeit IMHO not as powerful as Scala) and Scala is actually not that bad at performance either - probably better than a vast majority of other languages out there (albeit not as powerful as Rust or C++).

As far as ecosystems are considered - this is really hard to say. Rust has the whole C (and most of C++) ecosystem at hand plus a few really amazing Rust solutions like Cargo. Scala is limited mostly to JVM and JS, which are great ecosystems, but it is not true they offer everything. And there are still some caveats when using Java tools with Scala (e.g. profilers, debuggers or build systems).

Of course, general purpose languages have their limits and I'm not arguing that Scala can do everything. But it certainly can do a lot more than Rust (and its C/C++ interop) at the moment.

And I have no problem with the JVM interop. As long as you don't try to write Scala libraries that need to be consumed by Java applications, everything just works really.

A lot more than Rust? Do you count the whole embedded, low latency and scientific computing? As far as I like Scala, I don't think it is better in these areas than Rust. Even the async I/O libraries are more advanced in Rust than in Java.
I'd say Scala's support for scientific computing is significantly ahead of Rust; there are good libraries for ML, linear algebra and what have you (including relatively good bindings for native LAPACK), and there's the whole Spark ecosystem.

Scala on a well-tuned JVM can reach much lower latency than you might think. Yes, in theory you'll hit a limit that Rust could help you go beyond. In practice, are you ever going to reach that limit?

Rust's embedded support is mostly theoretical at this point, and the use cases for old-school embedded are increasingly limited. Want to write some code for that cheap off-the-shelf SoC you bought? Scala is probably a perfectly good option, because it's probably an ARM with at least 1Gb of RAM and the JVM will run there just fine. If you're actually writing something that has to run for months off a single charge then yeah, Scala won't cut it whereas Rust may one day be able to. But most "embedded" these days isn't like that.

> Scala is limited mostly to JVM...

No, it's not. You can call native (C) functions from the JVM.

Of course you can call C functions, but it is neither ergonomic nor performant. And good luck exchanging more complex data structures. Java can't use C structures directly, Rust can.
You still need a JVM, which what the GP is trying to point out (I think...)

That said; nowadays you have the Native and JS runtimes as possible targets as well. Scala.js works surprisingly good with very few caveats.

Out of context yes. I should have said that with respect to which language pick up next and which language has a bigger future potential. Rust is so omnivore and hits on so many critical aspects that it is hard to deny it. Given it had rich and mature ecosystem now how many would pick Scala? Those who prefer GC pauses and have a hard dependency on JVM? But even then you are pitted against Kotlin and Java itself. I would say quite tough times are awaiting Scala 3 and not only on individual level but on company level. It is still unclear how much the migration will take and whether it is worth it in the first place.
I don't exactly see how Rust can replace Scala where Scala is mostly used today. Or even Swift, despite Apple being more serious on the server-side roadmap lately and hiring a bunch of people from the JVM world.

Kotlin doesn't bring anything new and is full of ad-hoc design decisions, and Java itself is catching up (and even potentially surpassing it, see pattern matching for instance). I don't think it's a huge threat. Sure you'll see people who didn't really understand the point of Scala move to Kotlin because they just wanted a better Java really, and Kotlin does that well. But I think it's been clear that Scala shouldn't try to pursue this goal, for the past 5 years at least, after the hype around Spark cooled off.

I'm happy that Scala 3 is focusing on the biggest pain points. I'm also quite happy with the resources that have been added behind the tooling and ecosystem lately. The Scala center wouldn't exist if there wasn't a real demand for improvements in the mid to long-term future.

> compiling times force you to leave your seat for a cup of coffee

This is not the case in a codebase split into multiple modules that compile in parallel.