Live data from Hacker News

True Scala complexity

yz.mit.edu

111–120 of 152 posts

Re: True Scala complexity

#111
post #65

If anyone is able, would you please explain to me these two questions from the quiz? I’m stumped. Why does `toSeq` compile, but not `toIndexedSeq`? Set(1,2,3).toIndexedSeq sortBy (-_) Set(1,2,3).toSeq sortBy (-_) Why does `h` compile, but `f` does not? def add(x: Int, y: Int) = x + y val f = add(1,_) val h = add(_,_)

toIndexedSeq takes a type parameter (I'm not sure why - it appears unncessary) which means the result of toIndexedSeq is not known when it is attempting to infer the Ordering. toSeq doesn't take one, it's known to be Seq[Int].

The other looks like some quirk of partial application. It's unlikely there's any fundamental reason, only an implementation imperfection.

Re: True Scala complexity

#112
post #77
post #74

Earlier quoted context omitted.

MultiParameterTypeClasses are a fine feature! It's the ones like UndecidableInstances that you have to be careful about.

They are, until someone makes a five parameter monster because "someday I'll want that genericity." To be honest, though, I just googled the extensions I could remember until I hit one that hinted at creating a situation where the compiler simply doesn't have enough information to compile your code. That said, yeah, UndecidableInstances look like a much better example. Especially when googling it turns up things like…

Is MultiParameterTypeClasses something like multible dispatch from CLOS?

Re: True Scala complexity

#113
post #101

Earlier quoted context omitted.

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…

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 :: , transform :: )
      let  = type-for-copy(coll); // analogous to CanBuildFrom
      let new-coll ::  = make(); // 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 :: , transform :: )
      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.

Re: True Scala complexity

#114

Fantastic post. The most salient excerpt for me: def filterMap[B,D](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B] def filterMap[B,D[B] Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B] def filter…

C#: public static class EnumerableExtensions { public static IEnumerable FilterMap (this IEnumerable list, Func > callback) { ... } } There are some methods that need to be part of the class and carried around with the instance so that things like polymorphism work. But many operations work perfectly fine without. By making those lexically scoped, you avoid the problems of monkey-patching and method collisions. Exten…

you have the same problem in C#. you can't take a Foo class and extend it from outside to have all the IEnumerable methods then let it have all the IEnumerable extension methods.

Re: True Scala complexity

#115
post #23

Fantastic post. The most salient excerpt for me: def filterMap[B,D](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D Option[B])(implicit b: CanBuildFrom[?,B,D]): D def filterMap[B,D[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B] def filterMap[B,D[B] Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B] def filter…

To me, a big advantage of Scala's "enrichment" over monkey-patching in Ruby or JS is that it isn't global. That is, you have to import the enrichment. Another code module in the program won't be unexpectedly affected by it. In practice, I almost never use monkey-patching in dynamic languages because it's too dangerous. While in Scala there are cases where enrichment won't work, you can always just write a regular fun…

Some languages make monkeypatching a lot less dangerous.

For eg. in Perl you can use dynamic scoping to localise its effect:

  {
    no warnings 'redefine';
    local *SomeModule::some_func = sub { say "MONKEYPATCHED!" };
    
    # now everything in this scope that uses or calls SomeModule->some_func 
    # will now use the monkeypatched version
  }

  # where has everything else outside this scope remains unaffected
In Ruby Refinements earmarked for ruby 2.0 will have something similar: http://www.rubyinside.com/ruby-refinements-an-overview-of-a-...

Re: True Scala complexity

#116
post #70
post #23

Earlier quoted context omitted.

To me, a big advantage of Scala's "enrichment" over monkey-patching in Ruby or JS is that it isn't global. That is, you have to import the enrichment. Another code module in the program won't be unexpectedly affected by it. In practice, I almost never use monkey-patching in dynamic languages because it's too dangerous. While in Scala there are cases where enrichment won't work, you can always just write a regular fun…

Adding and/or altering functionality at runtime isn't dangerous. Monkeypatching may be, so avoid that. Also, an entirely too-little used idiom (blame Rails programmers): module OverrideSomeMethod def some_method … end end s = SomeClass.new s.extend OverrideSomeMethod s.some_method

This is an idiom I use regularly (in Perl, Ruby, Io & Javascript) and come across it often in the Perl world where Moose roles are used.

The only downside of this idiom is the extra runtime cost which maybe an issue for Rails?

Re: True Scala complexity

#117
post #15
post #14

Earlier quoted context omitted.

I have to respectfully disagree. First of all, the things he shows aren't inherently complex: adding an additional function to the existing collections library is something that's possible in other languages in less confusing ways (see other examples in this post). The various concepts required to solve the problem in Scala like implicits and higher kinds might be inherently complex, but the problem he's trying to so…

"adding an additional function to the existing collections library is something that's possible in other languages" Is it possible in other statically compiled strongly typed languages? I don't think so? The advantages/disadvantages of static vs. dynamic languages seem out of scope for the "is scala too complex?" question.

> Is it possible in other statically compiled strongly typed languages? I don't think so?

Sure it is :) see C# http://msdn.microsoft.com/en-us/library/bb383977.aspx

Re: True Scala complexity

#118
post #84

Very good post. One point with which i cannot agree is "In fact, it is impossible to insert a new method that behaves like a normal collection method. " Please see http://ideone.com/ePUHG An excerpt: import MyEnhancements._ println("qwe".quickSort) println(Array(2,0).quickSort) println(Seq(2,0).quickSort)

Miles Sabin's solution is also worth a look: https://gist.github.com/f83892f65f63b14a1f75 It uses dependent types which will be included by default in Scala 2.10. I don't consider this as "simple" but my point of view is that Computer Science is not "simple" :-). And having a language supporting that level of genericity is really helpful for type-safety and code reuse.

@OlegYch's solution also represents a good trade off of complexity vs. convenience: his solution is simpler but doesn't get along so nicely with type inference; mine gets on just fine with type inference, but is more complex and depends (no pun intended) on dependent method types. As ever YMMV.

Re: True Scala complexity

#119
post #112
post #77

Earlier quoted context omitted.

They are, until someone makes a five parameter monster because "someday I'll want that genericity." To be honest, though, I just googled the extensions I could remember until I hit one that hinted at creating a situation where the compiler simply doesn't have enough information to compile your code. That said, yeah, UndecidableInstances look like a much better example. Especially when googling it turns up things like…

Is MultiParameterTypeClasses something like multible dispatch from CLOS?

no

Re: True Scala complexity

#120
post #101

Earlier quoted context omitted.

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 aft…

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.

Post reply on HN