Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

After reading a bunch of these articles and really walking away unimpressed this one is fantastic. It does a great job of showing how cryptic you can make Scala code. It also shows that the same topic on which half the article is spent explaining can be boiled down to four lines of less "Scala Like" code. As a pragmatic user of Scala I don't see why the fact that you _can_ do some amazing but cryptic things with the language is a detriment. Its like pointers in C or templates and multiple inheritance in C++. Some people say that the developer shouldn't be given such capabilities since it can lead to cryptic and possible dangerous code. I am of the opinion that the additional flexibility the features provides is nothing but a benefit and that its up to the developer/organization to make sane limits on what can be used.


"As a pragmatic user of Scala I don't see why the fact that you _can_ do some amazing but cryptic things with the language is a detriment."

Because you hit the cryptic too soon, and you can't really avoid it. And I generalize this to any such language that has this characteristic, not just Scala. You can't use C++ for very long without having to know huge swathes of it if you want to use the libraries created by others, and you want to be able to debug why they don't work perfectly. (Bearing in mind "working perfectly" also includes you having a correct mental model of how they work, which is tricky if you only have a subset of the language in your head!) Same for Haskell. Compare with Python, where you basically can learn a reduced subset of the language, yet still use libraries fairly effectively. You need to know the basics of having objects and calling methods. You don't need to know how to write your own iterator, any of the double-underscore methods or the resulting protocols, you probably don't need to know decorators (and even if you do, probably just how to use them, because they are unlikely to bite you in odd ways if you just copy-paste instances of them), you don't need to know metaclasses, etc.

Type systems of almost any kind are very hard to satisfy without understanding. In some sense, this is one of the very constraints the stronger ones are trying to enforce.


This is not my experience.

I don't give a dime about higher-kinded types, type bounds, type views, type constraints, CanBuildFrom ... and I'm happily using the language.


You must be one of those lucky people that only write new code all the time and never have to maintain code written by someone else. For the rest of us poor suckers who have to do a mix of writing new code and maintaining existing code bases written by other sadistic (!) developers then it's a very valid concern.


Some languages show that you can do these things in a simple way. For example I impmentet a Persisten Vector for Dylan (very common lisp like language) in about 3 sessions. It was the first time I wrote dylan code befor that I only read on dylan. Its working and fiels like a "native" collection allready.

I guess it would take me the same amount of time fully understand this article. Other people in this thread have shown how easy it is in other languages too.


I still have not found a single comment showing "how easy it is in other languages". Do you have a link?


I'm not Parent, but I think that this is his implementation: https://github.com/nickik/Persistent-Vector-in-Dylan/blob/ma... The last part (line 222+) is what defines it as a sequence in Dylan.

Dylan (and other multi-dispatch languages) don't have the problem of "adding methods to objects/classes", because methods are standalone entities (first-class, whereas in Scala the are not) that exist independently of the data they operate on.

so you can just "add" a method to something by defining it:

    define method upcase(s :: String) => (ret :: String)
      // code here
    end method upcase;

    "abc".upcase // => "ABC"
    // is just sugar for
    upcase("abc") // => "ABC"
so you could define 'forward-iteration-protocol (a method that returns 8 values) on anything in Dylan (built-in or not) to make it a "Sequence".


That has nothing to do with the issues mentioned in the original article.

In fact, the thing you described is trivial in Scala.

    implicit def Upcase(s: String) = new { def upcase = s.toUpperCase }

    "abc".upcase


I thought we were talking about "adding methods" to collection-like things so that they look as if they were built-in. And this was what the article was about: That it gets complex (and impossible) if you want to solve it for the general case.

It wasn't abut type-safety, just about complexity.

The problems in Scala arise, because you have to extort yourself if you want to "add a method" in the privileged position after the dot. You have to to resort to implicit conversions which are a non-composible feature. To fake composibility the author has to wade through huge piles of complexity.

These problems don't arise in Dylan at all, because there is no privileged argument position (the receiver) and you can just define methods for anything without conversions to wrappers or monkey-patching. Namespacing is done via the module system and lexical scope.

A collection-like thing in Dylan is any object where someone has implemented the required methods (first and foremost: forward-iteration-protocol). All those methods can be implemented without having access to the definition of the objects class or type, so things like native arrays (with only .length indexing and value setting as operations, akin to Java arrays) can be made collection-like.

Then you can just define your own methods for collections like filter-map, which will then work for thoose native Arrays.

If you only use the minimal collection protocol:

    define method filter-map(coll, pred :: <function>, transform :: <function>)
      let <ret-type> = type-for-copy(coll); // analogous to CanBuildFrom
      let new-coll :: <ret-type> = make(<ret-type>); // analogous to Builder
      let (init, limit, next, end?, key, elt) =
                       forward-iteration-protocol(coll);
      for (state = init then next(coll, state),
           until: end?(coll, state, limit))
        let e = elt(coll, state);
        if(pred(e))
          add!(new-coll, e));
        end if;
      end for;
      new-coll;
    end method upcase;
If map and choose (filter) are already defined (And yes; they are in terms of the collection protocol):

    define method filter-map(coll, pred :: <function>, transform :: <function>)
      map(transform, choose(pred, coll));
    end method filter-map;
Yes, I don't really like the API-design of forward-iteration-protocol. It works like iterators in Java, but is designed to not need allocation for simple indexable collections like lists and vectors etc.


Fully agree. I have not trieded this but I think the typesystem features dylan has should work too (limit and unions).

The most importend methods to expand are element (getting the n's object), forward-iteration-protocol and add. For a immutable collection that all you really need.


This is really, still missing the point. “adding methods” is trivial.

The real complexity is when you add method M to collection C and expect that class A which does not have any relationship with C also gets the method.

It has been shown that it is possible in Scala, without all the unnecessary complexity shown in the authors post.

Still, I fail to see any statically typed language even coming close to what is requested from Scala.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: