
Warts of Scala - manojlds
http://www.lihaoyi.com/post/WartsoftheScalaProgrammingLanguage.html
======
jakozaur
Hmm I would disagree that you its a problem that Scala requires explicit
partial method application.

------
paulddraper
> 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
I 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
Abstract 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.

------
Randgalt
Very 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.

------
tekacs
> ... 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
I 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
I 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
What 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

~~~
tekacs
Hm, so I wrote Python for a decade before Scala (and I still write TypeScript
& JS) and I'm deeply sympathetic to the 'passing around a callable' way of
looking at things. That said, I find Scala's syntax _more consistent_.

I would regularly run into that 'wart' in Python, where changing something
between a property and a method would completely change the downstream
behaviour (property -> method makes downstream 'calls' eta-expand and method
-> property makes downstream calls _error_ , or _call the eta-expanded
function_ if the property happens to return a function, both of which are
terrible IMO).

I guess more than caring either way about number-of-argument-lists, I just
disagree (as does a sibling commenter) with automatic eta-expansion, because I
think that 'people may forget to call `someFn _`' is unconvincing.

If you currently try to pass `someFn` bare, to something expecting a function,
you get a very clear error (and in some cases you can actually do this and
it's automatically handled, like in `.map(someFn)`).

On the other hand, I would find it deeply objectionable if the compiler
started magically switching the type of an expression of mine based on the
upstream's number of parens. If they change `def foo()` to `def foo` I don't
expect the type of my `val x = foo` to change from `() => Int` to `Int` or
vice versa.

