True Scala complexity
yz.mit.edu
yz.mit.edu
def filterMap[B,D](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D
def filterMap[B,D <: GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D]): D
def filterMap[B,D <% GenTraversableOnce[B]](f: A => 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] <: GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[B] <% GenTraversableOnce[B]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]], ev: D[B] <:< GenTraversableOnce[B]): D[B]
def filterMap[B,D[_]](f: A => Option[B])(implicit b: CanBuildFrom[?,B,D[B]], ev: D[B] => GenTraversableOnce[B]): D[B]
...
> The answer to our original question? It turns out none
> of these are correct. In fact, *it is impossible to insert
> a new method that behaves like a normal collection method.*
> This, despite the heavy advertising of enrich my library.
Stuff like this makes think about how, despite all of the problems with using it in libraries, it's lovely that many dynamic languages can be extended in your application with little fuss. # Ruby.
class Array
def filter_map
...
end
end
// JavaScript.
Array.prototype.filterMap = function() {
...
};(defmethod filter-map (f (a array)) ...)
The problem is adding it as an "instance" method to a collection and expecting that it is usable by something not being a collection at all.
(defmethod filter-map (f (c (eql some-particular-collection))) ...)
but I suspect that's not what you meant.
I also can't make heads or tails out of "expecting that it (which "it"?) is usable by something not being a collection at all." I'm not even sure that's proper English, let alone semantically meaningful.
Additionally, last time I looked Cl wasn't really statically typed. Has that changed recently?
No, CL is not statically typed. And your point would be...?
Ahh, the famous behavior of Lisp fanatics. Tragic, how it is obvious to everyone – except themselves – why no one wants to use their language.
> No, CL is not statically typed. And your point would be...?
Uh ... what about
a) Author complains about the inability of the compiler to prove some property of his code.
b) Untyped languages – by definition – don't provide any substantial proving abilities based on types.
c) Therefore, you are completely missing the point.
No, the author is complaining about the complexity of the language. Maybe you should go back and re-read the article. Start with the title.
> Untyped languages
Lisp is not untyped. "Not statically typed" is not synonymous with "untyped."
> Therefore, you are completely missing the point.
Which of us is missing the point remains to be seen.
My account was created 1458 days ago. Just how long does someone have to be here before you no longer consider them "new"?
Of course, I don't program in Scala, so my explanation may not be accurate.
That's right.
> So, it's an issue that only comes up in a statically typed language.
No, that's wrong. You can do type inference in non-statically typed languages. Lisp compilers do this all the time.
At this point I would like to remind both you and soc88 of a parable:
Patient: Doctor, it hurts when I do this.
Doctor: Well, don't do that.
(Soc88's response, in the context of this parable, is something along the lines of, "But anyone who doesn't do this is a moron.")
Inferring types at compile time is necessarily hard. It is a corollary of the halting problem that no static type inference can be perfect. Therefore you have the following choices:
1. A simple compiler that sometimes fails to identify type errors at compile time
2. A simple compiler that sometimes produces false positives (i.e. signals a type error in a program that is in fact correct)
3. A complicated compiler. (Note that even a complicated compiler will also do 1 or 2 or both, but potentially less often than a simple compiler.)
Those are your only options. Reasonable people can disagree over which is preferable.
OF COURSE static type inference is hard. That's a straightforward consequence of the halting problem. Pointing to defmethod is just an oblique way of making the point that perfect type inference is NOT NECESSARY for getting things done. You can choose to lament the complexity of Scala (and static type inferencing in general) or you can use Lisp or Python and trade certain compile-time guarantees for simplicity. Like I said, reasonable people can disagree over which is preferable.
What reasonable people cannot do is insist that there is a single perfect solution that is both simple and error-free. Anyone who believes that has not understood the implications of the halting problem.
Another thing reasonable people cannot do is frame the tradeoff as a binary choice: either you use static type inferencing, or you give up all compile-time guarantees. That is simply not true, as is amply demonstrated by e.g. the SBCL compiler. It's a complex, multi-dimensional space of tradeoffs in language design, compiler complexity, and different kinds of compile-time guarantees. It's INHERENTLY complicated. The best you can hope to do is find a reasonable point in the design space for your particular quality metric. For the OP, Scala isn't it.
Reasonable perhaps, but manifestly not ideal or he would not be complaining about how complex it is.
> he said something quite similar to what you said
Well, I didn't actually say much, I just posted a snippet of code and left people to draw their own conclusions. Why soc88 chose to start a fight I can only guess, but it seems to be not uncommon behavior among people trying to defend untenable positions.
> Those are your only options. Reasonable people can disagree over which is preferable.
Ah ok. Being right seems to be more important to you than having a honest discussion.
Have fun, I'm out.
It is easy to show that that will not solve the problem. If your language is Turing-complete, then you can embed (say) a Lisp interpreter and arbitrary Lisp code within it. The only way your compiler can be complete and correct for your language is for it to be complete and correct for this embedded Lisp. This is a fundamental result. There is no way around it.
So CL solves the problem without adding more complexity, whereas in Scala you have to extort yourself to shoehorn some functionality after the dot.
You lose static typing; strong typing is different: http://en.wikipedia.org/wiki/Strong_typing
enhancement MyEnhancement<T> : T[] { function filterMap() { . . . } }
Roughly the same in C# with extension methods, though with C# you explicitly import the extension method while in Gosu they're automatically always there (sort of good, sort of bad). Again, not exactly the same as the dynamic language examples, but enhancements/extension methods do allow you to extend existing classes in a reasonable fashion while still being amenable to all the other advantages static typing gives you.
The more complex method he is proposing (filterMap) cannot be done at all in the languages you mention, though he only uses it precisely to get the most complex kind of method that Scala collections offer. But it is also possible, and I just blogged about it here: http://dcsobral.blogspot.com/2012/01/adding-methods-to-scala....
But, no, that is not what he wants. He wants to add this method not to the collections, but to something that isn't a collection. Well, Scala can do that too -- it added all the collections methods to String and Array, didn't it?
And here comes the twist: he wants to add filterMap not by adding it directly to them, like Scala does. He wants, instead, to go _through_ that code to get at them.
With extension methods, the login would be like this:
* X adds extension methods to Y * Z adds extension methods to X * Therefore, Z extension methods should be available on Y
And, in fact, it is even possible to do that in Scala for many methods, but not for the particular combination he chose, and while still inferring all types.
People should first actually understand the problem, only after that a discussion about solutions makes sense.
Now the question remains: Should a language make almost-impossible and dangerous tasks easy or hard? I certainly prefer a language like Scala, which makes easy things easy, hard things possible and dangerous things hard, instead of the other way around.
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 function in those cases, and there are lots of cases where enrichment _does_ work...
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_methodThe only downside of this idiom is the extra runtime cost which maybe an issue for Rails?
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-... public static class EnumerableExtensions
{
public static IEnumerable<R> FilterMap<T, R>(this IEnumerable<T> list, Func<T, Option<R>> 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. Extension methods are fantastic for this.Also, for kicks, Magpie:
def (items) filterMap(callback)
var result = []
for item in items do
match callback(item)
case true, mapped then items add(mapped)
else nothing
end
end
result
end
var result = [1, 2, 3, 4, 5, 6] filterMap with
if it % 2 == 0 then (true, it * 2)
end
print(result) // 4, 8, 12
Magpie is dynamically-typed, but methods are lexically-scoped (and are multimethods).The existing collections functions in Scala take you very, very far. If you need to write your own primitives for performance reasons, it can get tricky, but that's really uncommon. Odersky's book (chapter 25) explains how to do that.
Collections libraries are hard in Scala because of what you get. Once you define a few simple functions and possibly implicit conversions, you get all 50+ sequence methods "for free", and if you do it right, your map type functions will return collections of the same type (runtime and static) as the original-- without explicit typing. You get a lot of leverage, but you have to work a little bit for it.
type 'a IEnumerable with
member this.filterMap f =
[for x in this do if f x <> None then yield (f x).Value]
let j = Map([("a",1) ; ("b",2) ]).filterMap(fun kv -> if kv.Value = 1 then Some(kv.Key,kv.Value) else None) |> Map.ofList
let n = [1.;2.;4.].filterMap(fun x -> if x % 2. = 0. then Some(x**2.) else None)
The extension works with any Sequence (arrays, sequences, maps,etc.) with the caveat that it maps every enumerable to a list. You could also define an extension for a specific type where a broad definition does not make sense.Personally, I lean more functional, I have never run across a need for extending things in this manner.
The requirement was to add a method that works for all collections, whether platform or language specific, while preserving type. The code I gave is an approximation of a solution - to use a rough analogy: topologically speaking the code matches but loses the geometry. The code I gave works on basically all .NET collections, whether C# or F#, string or tree - as long as they implement the interface - they are matched. That it leverages the existing organization should not count against it. The failing is that although types are preserved it is under a new geometry or structure.
Or what am I missing?
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.
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".In fact, the thing you described is trivial in Scala.
implicit def Upcase(s: String) = new { def upcase = s.toUpperCase }
"abc".upcaseIt 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.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.
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.
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.
I don't give a dime about higher-kinded types, type bounds, type views, type constraints, CanBuildFrom ... and I'm happily using the language.
"Simple things should be simple, complex things should be possible." - Alan Kay
I don't think the blog author gives particularly great examples of simple things that are made complex by the language. He simply gives examples of things that are inherently complex that scala at least makes possible.
Secondly, excusing complexity by saying "it makes things possible that wouldn't otherwise be possible" isn't really enough of a justification. The question is: do those exact things need to be possible? Or is there some way that gets me 90% of the way there without the complexity, and that's good enough? More power isn't always better, which was a big part of the point of the article. Just dismissing it by saying "well, the complexity lets you do powerful things" doesn't really refute the point of the article, it totally misses it.
There's a tradeoff to be made. You might make those tradeoffs differently than I would, which is totally understandable, but we should at least be able to have a conversation about the fact that there is such a tradeoff without people dimissing statements like "Scala is complex" out-of-hand.
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.
This is completely non-extensible and people have to pay the price for this syntactic sugar (e. g. extension methods not discoverable with Reflection).
Other languages have a much cleaner approach: traits (in languages like Scala) and default methods (in Java) both solve the problem in a more straightforward and correct way.
So it doesn't really seem like they solve the problem "how do I add a filterMap function to all arrays or collections that works pretty much how I want it to" any simpler than Scala. All the complexity that the author brings up in his post is because he was trying to add this functionality with a single method (and a bunch of implicits).
But the problem is: put in a string and it gives back a list of characters. So I use the term it is "topologically correct" heh. To be honest I do not think there is anything functional about what he is attempting but perhaps the idioms in Scala are different? My knowledge of scala is mostly horizontal (from Ocaml,F#, haskell).
Sure it is :) see C# http://msdn.microsoft.com/en-us/library/bb383977.aspx
It's very easy to write a new function for your particular case. You can write an acceptable map function for your new collection, or a new list-munching function, just as easily as in ML. It will be a top-level function instead of a method, but that's usually just fine (if inelegant).
The long-term risk you are taking is that you might want this list-munching function to apply to other collection types and will now have code duplication.
What Scala offers is the ability to write new methods that apply to all collections (Strings, BitSets, Lists) if you wish... and to create new collections types (with a little bit of work and comprehension, but not as much work as is involved in writing 50+ library functions by hand) that automagically end up with all the collections functions.
This impressive and quite radical code reuse is the part that's hard about Scala, but that's only relevant if you want to write libraries at the L2/L3 level of quality and beauty.
Scala offers a particular way to solve that problem that has its own plusses and minuses, but it's not the only way for a language to let you do that, and it's certainly not the "simplest" way to accomplish that goal.
> "I want to add a method to all my collections that works like the existing methods," in Scala it requires an understanding of a huge number of complicated intersecting concepts, whereas in other languages it doesn't.
Sorry, but there is a bit more to it. I will gladly accept a link to a language which does all the stuff he was trying to do, but until then I'm not convinced at all.
1) a simple mechanism for "enrichment" (aka. retro-active extension, virtual classes)
2) functional type-level computation (as opposed to the mini-prolog engine that is implicit search + type inference).
Reducing complexity is complicated, unfortunately. We [have been|are] thinking about both of these alternative features, though.
---
ps: The following part of the article is inaccurate: "Turns out that Scala will search up to one level up the type hierarchy for a matching shape for the implicit."
Check out the "implicits without the import tax" part of http://eed3si9n.com/implicit-parameter-precedence-again. The implicit scope includes all the superclasses (and their companion objects) of all the parts of the type of the implicit value that's being resolved.
http://webcache.googleusercontent.com/search?q=cache:http://...
eg. The following fails:
Set(1,2,3).toIndexedSeq sortBy (-_)
But doing the same in 2 steps, ie. after assignment, works
val xs = Set(1,2,3).toIndexedSeq; xs sortBy (-_)
I have been bitten by this several times now, so I don't unnecessarily chain > 2 functions even if it does compile ( which also solves one other brainteaser posed in that article ) Have also seen the problem with the add function, when I wrote a matrix manipulation library.
def add(x: Int, y: Int) = x + y
add(1,_) fails, but add(_,_) works. Even though the error message " missing parameter type for expanded function" seems reasonable, and providing the parameter type ie. add(1,_:Int), does compile, the bahavior is hard to explain to newbies.
His point about hanging your hat on asInstanceOf[...] and the resulting code being clumsy...ok, guilty as charged, but then you only have so many hours in a day, and mgmt is paying $$ to solve boring business problems ( compute the asset quality of five million loans in thirty lines of business over twelve quarters using scala ) and not mucking around ( add a filterMap to the scala collection library to get an idiomatic Scala implementation )
I enjoyed the programming snippets in the article very much, but I still don't get what the point of the rant was. He goes on and on about "lack of acknowledgement of complexity", but what does that really mean ? Does he want like a gold star ? Even if everybody acknowledges that scala is complex, what then ? Eventually some of it will get fixed & the rest will not & life will go on. Why be a downer at such an early phase of growth of the language ? All this talk of excess complexity will simply scare off the early adopters. Java has been around for 15 years and we still don't have decent generics. Scala is so far ahead in such a short time. Patience, etc.
Because things don't get fixed if people won't talk about them. Irrational defenses (like calling someone who points out legitimate problems a "downer") are a hindrance toward making Scala better than it is right now.
Second, I think the blog post is useful because it shows what's wrong with some (fortunately very small) part of the Scala ecosystem, and because it points to a way to fix it.
Here's a quick recap: The author tries to add arbitrary operations to Scala's Seq abstraction without changing its source code and wants them to work also on arrays (which are plain old Java arrays), without any extra work. Arrays in Java support: length, index, and update; that's it. There is as far as I know no language in existence that allows the precise thing the author wants to achieve. And there are many variations, such as adding only to Seq or only to Array that would be really easy in Scala but still impossible in most other mainstream languages. The author then throws all the machinery he can think of at the problem to still achieve the same non-result. Well, tough luck. He might have hit a thing that's simply impossible to do in a generic way, given the tools we currently have. In fact, I have not checked whether there would be a way to achieve the result that he wants because that's beside the point. There are always limits to a generic formulation that will force you at some point to treat things on a case by case basis.
The problem is that, in trying to achieve his impossible goal, the author (mis-)uses a lot of the most powerful features of Scala, and concludes that Scala is simply too complex for helping him achieve the result. I believe he wrote this blog post to prompt the maintainers of Scala to add even more power to the language so that he can achieve his goal (the only other motivation I can think of is that he's trying to actively damage the ecosystem he writes code in, but that would make no sense to me).
My response will probably not please him. I think that we need to take away sharp knifes from people who have a tendency to cut themselves. I was always proud that in Scala you could do in a library where in other languages you had to change the compiler. Inevitably, some of this is cutting edge stuff. We have tried many times to clarify the boundaries, for instance when I defined the Scala levels
http://www.scala-lang.org/node/8610
But we can't prevent a developer who prides himself to "stroll right through level L3" to get hurt.
So, I believe here is what we need do: Truly advanced, and dangerously powerful, features such as implicit conversions and higher-kinded types will in the future be enabled only under a special compiler flag. The flag will come with documentation that if you enable it, you take the responsibility. Even with the flag disabled, Scala will be a more powerful language than any of the alternatives I can think of. And enabling the flag to do advanced stuff is in any case much easier than hacking your own compiler.
I would be interested to read your comments on this proposal.
Yes, I love the idea. I've said so before on hackernews and on the scala-user mailing list; it would be a huge contribution to Scala.
Most of these features (implicits, higher kinded types, and hell, it's not complicated but ugly so I'd throw it in: using symbols as method names) are useful for library designers, but less so for your average project.
I've written a few thousand line Scala application that has so far made awesome use of first class functions, pattern matching, and actors. I can't think of another language that would work as well for me, and I haven't had to resort to any of the advanced features the author talks about.
What i'm terrified of is having patches/contributions that make use of these 'complex' features that might scare away newbies from hacking on the project.
I think having a 'version' of Scala (enforced by the compiler) that was as simple for beginners to use as Gosu, Kotlin, Ceylon etc. would go a long way in Scala evangelizing, and unlike the aforementioned languages, they wouldn't be as constrained as they grew as developers.
My view is that Scala is what it is. It provides a set of powerful features to solve hard problems. Some features are conceptually weighty, but they are all there for a reason and constitute a logical whole.
Don't they?
Speaking as someone who sometimes writes controversial things and never enables comments on my posts...
I view collections of comments as discussions. Readers often form into communities, like Hacker News. If there are comments on a blog post, are you supposed to write your comment here in your community? Or there on the post? Well, a good question is, are you a member of the author’s community? Probably not! Communities have all sorts of standards for participation. What are the author’s standards? Why bother figuring them out for one comment?
Communities also have other tools like seeing a person's history of posts to get a sense of their viewpoint and biases. How do you do that on an author’s blog?
Ultimately, authors either have to get rid of comments or buy into a plug-in system like Disqus. In my own case, I usually just provide a link to HN for posts that I think are of interest to the folks here. Everyone else can use twitter, reddit, or their own blogs to continue the discussion.
That doesn’t mean I won’t read all the flames, there are no end of analytics tools for discovering what people are saying about my writing.
I don’t speak for this author, but considering he has a link to this discussion right at the top of his post, I’m guessing he’s listening to your thoughts and welcomes your feedback right here or wherever people congregate to discuss the news of the day.
I know I frequently click on the links people provide when they make blog comments, but I don't think I've ever looked at someone's Disqus history. Disqus cuts across multiple sites in potentially drastically different arenas; it's too horizontal. But when someone provides a link, they usually mean it to be relevant.
Haskell (well, the GHC compiler) has been doing this kind of things for ages. They even take the further step (which I agree with) of requiring that "dangerously powerful" features be explicitly enabled on a per-case basis. They also have a syntax for enabling features on a per-file basis, which reduces the likelihood of advanced techniques creeping into other parts of your codebase.
I think your proposition is a good idea, and heartily recommend a review of how the GHC team has adressed this problem.
I think Scala has a few tools that, like multiple inheritance in C++ or Haskell's MultiParameterTypeClass, need to have a bit of a warning label so people avoid them until the situation screams for it.
That said, yeah, UndecidableInstances look like a much better example. Especially when googling it turns up things like this: http://lukepalmer.wordpress.com/2008/04/08/stop-using-undeci...
That's where the similarity ends, though. CLOS does this at runtime during method invocation, MPTC "dispatch" is determined at compile time. Multiple dispatch will generally have some overhead that MPTC won't, but it's more flexible in that the set of function implementations can be extended without recompiling.
Also, due to limitations of type analysis or type expressivity, it's possible to write something that would work for the specific instances where you are using MPTC but can't be proven to work for all instances of the types involved. Then again this is almost always an issue with static typing. Wether the compiler is saving you from a bad decision or keeping you from doing your job is a matter of personal opinion.
You might be intressted in this paper I read just a couple of days ago: "Extending Dylan’s type system for better type inference and error detection". (Dylan OO is simular to CLOS) A system like that helps to get the dispatch overhead down while still beeing able to extend it at runtime and you get alot of the typesafty.
[edit s/less well/more well/]
If people/teams decide they don't want to use aspects of the language then they are free to do so by convention.
Giving people a mechanism to limit, or at least easily discover, the use of complicated language features sounds like a PR win for Scala.
You may instead find people claiming that the complexity "isn't a problem" or "can be avoided by convention." I don't fully agree with either of those statements. A "complexity safety switch" (nice term, by the way) helps to alert programmers that more difficult language constructs are at play in a given piece of code. Identifying and advertising the use of problematic-but-useful language features seems like a good compromise between omitting them and allowing people to naively wander into them.
Are we sure that's true? In the first half, with RichSeqs and isMajority() and so on, the author appears to define all her own types, and gives her examples entirely in terms of those types. For example:
scala> class Seq[A] { def head = 0 }
defined class Seq
The built-in collections library doesn't seem to come in until filterMap(), AFAICT. scala> class Array
defined class Array
scala> class WrappedArray(xs: Array) extends Seq // a Seq adapter for Arrays
defined class WrappedArray
scala> implicit def array2seq(xs: Array): Seq = new WrappedArray(xs)
array2seq: (xs: Array)WrappedArray
scala> new Array().ident
<console>:13: error: value ident is not a member of Array
new Array().identI think that's the key takeaway: implicits aren't really a way of extending a library type, they just have some of the same effects. You still have to care about the distinction between the original type and the enhanced one, and that's, well, complex.
Correct me if I'm wrong, but he is not operating on native Java arrays at all.
He redefines Array, redefines Seq and redefines WrappedArray as a subtype of Seq. He adds ident to Seq, and since Array isn't a subtype of Seq, ident is not a member of Array ( he points that out too, and provides a solution as well. He is operating in his own "tiny parallel universe" as he says in the post. He even redefines String and CharSequence (which threw me off because isn't java.lang.String & java.lang.CharSequence already in the namespace so the conflicts etc...) Then he wants a Seq[Char] ( not the scala Seq but his redefined Seq ) to mimic the redefined String! The whole thing is quite crazy & terribly fascinating imo.
Scala brings a lot to the table as a "java with less painful syntax and some functional programming tools", IE level A1/A2/L1. That's enough to inherit a huge legacy from Java, and if there's a way to allow that to happen without killing off the more advanced features, it seems like a win-win.
As a library developer, let's say I use a few implicit conversions to make my library easy to use. I sign off on the use with a compiler flag, no big deal, but now every user of my library has to do the same?
Perhaps I'm misunderstanding, but wouldn't this suggestion effectively ruin "ArrowAssoc"? Because I can't think of a single application I write that doesn't use one of those, honestly.
I assure you I didn't stroll through L3. I've been using Scala for half a decade and I'm still learning it.
Your reply is a bit disheartening. I don't know how else to convince you, as a long-time user of your product, that every single one of the issues I listed has been a "real-life" encounter. Dismissing the ends as impossible and thus my path toward discovering this fact as mis-using the language suggests to me that you may be overly fixating on the specific example goal I used; it's only one sample point out of many I could have chosen. That it's impossible to add such a method is not even close to the main point I'm trying to make; it's everything that came before that.
This post was neither a request for more expressive power nor an attempt to sabotage the community. The goal was much more modest: to just get something off my chest and to hopefully nudge the discussion past "is Scala complex." I would further submit that if you think I'm advocating piling in even more features/power, then I've done a woefully inadequate job communicating my thoughts. My current belief is that the solution involves either less power or a simply different set of tools.
As for your proposal, I'm not sure how it solves the problems:
1) You can always restrict yourself to writing in only subsets of the language, but this falls apart as soon as you interact with any code that isn't yours.
2) With all due respect, the idea sounds to me like it's tucking a bunch of features away behind a flag and discouraging users from voicing concerns or thinking critically about these features, which is really all I'm trying to do here.
As someone who struggled myself for a long time with the design of collections until the pieces fell into place, I can understand your struggles very well. In every language there is a limit of what can be achieved, and there is a grey zone before that where things get messy. I believe that Scala collections pushed the envelope in terms of flexibility and ease of use. But repeating this feat by extending all kinds of collections and collection-like structures generically with your own operations is not at all trivial.
You are absolutely right to point out when things get messy of course, even if it would be only to serve as a warning to others who might follow you.
The question is how to avoid similar experiences in the future. One can either change the language to make simple what you found hard. But before I buy into that, I would like to see a constructive and complete proposal what one should change.
Or, one could make a better effort to delineate the limits and the grey zone. That's what I proposed. Importantly, my proposal would only apply to definitions not to usages. So I'd put implicit conversions and definitions with higher-kinded types behind a flag, but not their uses. I hope this would give pause to library designers that mix advanced features with too much abandon, but it would not hinder users of these libraries at all.
If there was a safe, 'simpler' subset of Scala, I think you'd see a lot more adoption. And hell, once you get a job there, you can always make a good case for using some of the advanced stuff in certain parts of the code.
Unfortunately in the world we live in, not every programmer should be given the tools to let them write their own DSL as a means to accomplish their day to day tasks..
How is Scala more powerful than Haskell, Agda, or Coq?
Consider the implementation of a properly lazy "const" function in Haskell:
const x _ = x
And in Scala:http://apocalisp.wordpress.com/2010/04/21/a-proper-constant-...
Not sure about Coq, but Agda is Turing Complete (you just need to turn off termination checks) and Epigram also supports general recursion as well as structural recursion with total functions.
I was trying to point that the "more powerful" criteria is pretty subjective.
The OP said "more powerful than the alternatives", that usually means "runs in the JVM"... and AFAIK, Jaskell or CAL are far from mature.
Just about every modern Scala library depends upon the "implicit typeclass pattern" - many of which require that you define your own typeclasses for your own code, or at least are able to correctly create instances of framework-provided typeclasses in the appropriate implicit scope, if for no other reason than to help out the Scala compiler when it can't correctly resolve an implicit. If you start to push implicits behind a compiler flag, this will mean that fewer people will be familiar with their subtleties, and will consequently become more utterly lost when the framework provided to them becomes insufficient to satisfy their goals.
Indeed, I'd suggest that the entire history of the advancement of the Scala library ecosystem could be described as the process of the community learning to use implicits to greater and greater effect. It makes no sense to me to hobble developers by marking a feature "off limits" by default - I know that for my own usage, Scala's benefits only really became evident once I understood implicits; before that, it was simply a slightly better Java. With implicits, it's a tool that makes Java look like COBOL.
I would be interested to read any examples you may have which demonstrate this contrast.
I think the source of the problem, at least in this blog post, is that extension methods are limited today. They are done with implicit conversion which is limited for obvious reasons I guess (not allowing type A to arbitrarily become B). I think a feature focusing just on adding extension methods is required, even if it makes the language spec larger.
Scala is a functional/object-oriented hybrid, making it more complex than a purist language in either mold. It always lets you do things in a Java-like way - you can use it as "Java with less boilerplate" and touch almost nothing Scala-specific. Then it adds functional programming alongside.
To me this feels very natural; I like objects for the big-picture structure, but I like to write algorithms and manipulate data in functional style. If you need raw performance in some hotspot, write a Java-like while loop with mutable state; otherwise, write something nice and high-level (and the JVM will still be much faster than a "scripting language" however you define it, e.g. http://blog.j15r.com/2011/12/for-those-unfamiliar-with-it-bo...).
Scala does fix some Java warts that are legitimately complex or confusing in their own right. For example, primitive and boxed types are less strongly separated; there's no "static", just nice syntax for singleton objects; collections are 100x nicer with far less noise; a nice multiple inheritance design; covariance eliminates a bunch of nasty hacks; better ways to specify access controls; there's a decent way to factor out exception handling; case matching is _awesome_; and _so_ much less boilerplate in general.
I'm not sure the static vs. dynamic religious war can ever be resolved, but static types feel less broken and less verbose in Scala than in Java. Java makes you lie to the type system, or do something unnatural, much too often. Scala hasn't cured every such situation, but it's cured a lot of the most common ones, and greatly reduced the need for manual type annotations.
Scala gives you the conciseness of Ruby, but with static type checking, higher runtime performance, and interoperability with existing Java code.
Some tradeoffs of static type checking remain, such as compilation times.
People do go on wild goose chases trying to push the language farther than it's ready to go. I've done it myself. I agree with the article that there are lots of areas to improve and appreciate the constructive write-up.
But on the other hand, the perfect shouldn't be the enemy of the good. I certainly would not choose to go back to Java, even as I'd love to keep seeing Scala get even better.
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(_,_)The other looks like some quirk of partial application. It's unlikely there's any fundamental reason, only an implementation imperfection.
There's also a new docs site and it's getting better all the time: http://docs.scala-lang.org/
http://www.artima.com/pins1ed/
There's also a book manuscript (downloadable from typesafe.com, and Manning.com has 3 books in the pipeline, tho the 3rd, on FP by Tony Morris doesn't show there.
In the simplest form, you can use Scala as a better Java. It's worth exploring it just for that comparison. And then there's much, much more you can do, if you want to, but it's also a very nice language if you don't try to add methods to the collections library and don't use dependent types.
The difference imho is that Scala doesn't punish you for trying to be fast where necessary.
Apart from that I would really like to where Groovy or Kotlin are "more elegant or simpler". I would have probably looked into the specification but something like that doesn't even exist for Groovy. From my last journey into Groovy I learned that this language is substantially underdocumented and buggy as hell. I prefer not touching it anymore.
Some things in Scala are easy and some things are hard, but it's not always clear why. I'd expect the "good" things (whatever is considered good by the language designers, their expert opinion, that is) to be easy and the "bad" things hard. But in Scala it seems that whether something is easy or not depends on how well it fits with the language's algebraic model, and not how well it fits with recommended practice. Immutable is easy (that must be good, no?), but mutable is just as easy (so, wait, I'm supposed to... what exactly?); functional is easy, but so is imperative; implicits are easy but extension methods, as the blog post shows, can be really hard; type inference is easy except when it's not. See the problem?
No. Must be because I actually use the language instead of trying to bash it to advertise another language. :-)
I'm still excited to see another language solving the problem mentioned in the article. At the moment it really looks like as Scala gets bashed for the complexity of something not possible in any other language out there.
But I don't agree with your analysis. I won't go into the question of whether or not the feat mentioned in the post is possible in other languages or not, but I do know that if I came to a rather experienced Scala programmer and asked him off-hand if the tasks attempted in the post are easy, I suspect that the answer would be "yes". Scala is just... surprising like that.
Please see http://ideone.com/ePUHG An excerpt: import MyEnhancements._ println("qwe".quickSort) println(Array(2,0).quickSort) println(Seq(2,0).quickSort)
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.