5 ms·
Warts of Scala
- jakozaur 9y agoHmm I would disagree that you its a problem that Scala requires explicit partial method application.
- tekacs 9y ago> ... methods should be called with as many sets of parentheses as they are defined with (excluding implicits), and any method call missing parens should be eta-expanded into the appropriate function value. > Notably, removing the "optional parens" thing would not stop you from defining "property" functions that are called without parens; it would just mean you need to define such functions without parens, as is already possible. > In other languages with first-class functions, like Python, this 'works fine' [quotes mine] I'm not sure how this behaviour could be seen as 'more consistent'. If methods that have parens but are called with them stripped are eta-expanded but you can still define no-paren methods, you'd still have confusing behaviour where a no-paren method isn't eta-expanded as expected (because it's a property and so 'special'). Rather than more consistent, this behaviour is just 'more like Python'[0], which has this inconsistency too[1]. Essentially the suggestion in the article would mean: @ def x() = 1 @ val y = x resXX: () => Int = $... @ def a = 1 @ val b = a resYY: Int = 1 It's also worth noting that IntelliJ IDEA (and possibly Eclipse) warn you if you define a no-argument method with Unit return type (since `def action()`s should have parens and `def pure` values needn't). IDEA will warn you (and offer to correct) if you call a method with too few parens (`def x()` called as `x` will display a very visible warning), so maybe there's some room to believe that this is a 'problem', but the solution involving automatic eta-expansion doesn't seem like a particularly sane one. Also, calling `getFoo` without parameters is 'nice to have', but calling `name()` as `name` and `color()` as `color` on imported Java APIs both comes across as more natural in Scala (since Java has no properties, they can't be defined as properties upstream) and has allowed quite a few Java APIs to fit in naturally in Scala where they wouldn't have otherwise, in my experience. [0]: All the eta-expansion things in this post are to 'be more like Python', which makes sense given Haoyi is also behind https://github.com/lampepfl/dotty/issues/2491 https://github.com/lampepfl/dotty/issues/2491 and https://github.com/lihaoyi/Scalite https://github.com/lihaoyi/Scalite and (say) https://github.com/lihaoyi/macropy https://github.com/lihaoyi/macropy. [1]: Properties in Python also have the underlying design of using descriptors, which are a whole other world of horror.
- lihaoyi 9y agoI don't see the problem with val b = a b: Int = 1 After all, if you wanted to turn it in a function, you can use `() => a` syntax which everyone is already familiar with.
- tekacs 9y agoI don't think it's necessarily wrong, I just think it's inconsistent (all methods are eta-expanded... except properties), just in a slightly different way. Depending on what this did on conversion to Java-land, this could still be confusing for beginners: `int foo() { return 1 }` in Java could either: @ def x = foo resXX: () => Int = $.. // Why was this eta-expanded - it has no parameters?!? @ def y = foo resYY: Int = 1 // This is 'normal'... but it wasn't defined as a property? You could also argue that right now, if you want to turn any method (lacking generics) into a function, you can use the `someFn _` syntax, which in fairness perhaps not everyone is already familiar with. Also, removing parens from an upstream method would change the downstream type, which seems more confusing to me from a 'statically typed language' perspective[0] than up-to-N-parens all having the same type - the rule right now is simple to explain as 'you can drop as many empty parens as you like and the call will stay the same (but IDEA will warn you)'. [0]: In your scheme: @ def foo() = 1 @ val x = foo resXX: () => Int = 1 @ def foo = 1 // Upstream author changes their mind @ val x = foo resXX: Int = 1 // ??? @ val x = () => foo // Have to go around changing to this. ... and vice versa (if an author adds parens, all of your property calls start magically eta-expanding instead)
- lihaoyi 9y agoWhat you say is correct, but I think your mental model isn't the mental model many people work with. One possible way of thinking of things is "methods" and "function values", where you need to eta-expand something to go from "method" to "function value". This is something people get used to when working in Scala for a while. Another way of thinking of things is "things you can call" and "things you cannot call". In this case, it does not make sense to care about "method" or "function value": if it's callable, it should remain callable in the same way as I pass it around, until I call it. If you come from a Python, or JS, or other background, this is the mental model you will have. I personally think the second mental model makes much more sense, and the first is a weird artifact of programming in Scala that you don't see much elsewhere (though I admit C# has a similar dichotomy) I don't find the "people may forget to leave off parentheses" a convincing argument. People may forget to call `.flatten` on nested lists too; doesn't mean we should go flatten everyone's lists for them automatically. Furthermore, if you as an individual wanted to flatten `() => T` into `T` automatically, you can easily do that within your own project with an implicit conversion: implicit def flatten[T](f: () => T): T = f() The same way you could use an implicit to automatically `flatten` nested `List`s, or automatically call `.get` on `Option`s. Both of those are similar to automatically calling functions to avoid "confusion", and something I think most people in the community will agree would be a terrible thing to do
- paulddraper 9y ago> For example, the Ammonite script-runner (which is a medium-sized command-line tool) spends up to half a second just classloading That's not just because of the JVM; idomadic Scala generates volumes of byte code, due to anon functions and traits.
- mrkgnao 9y agoI understand that traits are something like typeclasses or interfaces: are single-method traits kept around as traits, or is the function itself passed around in the optimized output? I'm actually suddenly curious about how other functional language compilers look compared to GHC.
- paulddraper 9y agoAbstract classes can be efficiently encoded, since the JVM understands that. The Scala collections collapse common sets of traits into abstract classes. Traits are copied, always I think. Only traits with all final methods could even possibly be not duplicated.
- lihaoyi 9y agoAmmonite ends up loading about 2000 classes to run a cached script (i.e. no compiler required, no parser required, ...) $ cat foo.sc println(12345) $ java -jar -verbose:class $(which amm) foo.sc | wc -l 2015 Hello world already loads about 400 classes: $ cat foo.java public class foo{ public static void main(String[] args){ System.out.println(12345); } } $ javac foo.java $ java foo 12345 $ java -verbose:class foo | head -n 5 [Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home/jre/lib/rt.jar] $ java -verbose:class foo | wc -l 420 Now, 2000 classes isn't nothing, but it's not a particularly large number. Any medium sized application using a few third party libraries easily reaches that number.
- Randgalt 9y agoVery interesting article. In my experience writing Scala for the past year or so this article does indeed hit on many warts. But, there are many more warts not mentioned. The biggest two are the tooling (sbt for example) and the nearly incomprehensible standard library.