I agree that specialization right has some issues. But some of the
problems you list are worse than others. For instance, it would be nice
to be able to specify solutions for particular types, but the inability
to inherit from specialized classes is a much bigger issue.
Also, as a library author you leave out the biggest issue that I've
faced with specialization: if I expose an interface that has a
specialized type parameter, I have to document this fact to my users,
who in turn have to remember to "specialize through" to the place where
the type param is provided. It's easy to mess this up, and there's
nothing (other than poor performance) to clue you in to what happened.
In my particular case, this has made working with a specialized Numeric
library much less attractive... because I'm forced to choose between
beautiful and fast code.
> (5) Generic numeric code is awkward and low-performance.
> Solution: specialize.
> Problem: see (1).
Have you looked at https://github.com/azavea/numeric? After a bunch of
testing and profiling it gets good performance (e.g. comparable to
direct code) under Scala 2.9. I'd be interested in your comments or
critiques.
Tom Switzer and I are working on a series of proposals dealing with
Numeric (as well as numbers and math in Scala more generally).
Hopefully we'll be able to share that soon.
-- Erik
On Tue, Nov 15, 2011 at 12:28:32PM -0500, Rex Kerr wrote:I agree that specialization right has some issues. But some of the
> (1) Specialization. The basic concept isn't too bad, but the
> implementation is so full of bugs and limitations that employing it in
> anything but the most straightforward cases is painful at best. I really
> don't know how to solve this except for serious manpower to be devoted to
> solving the problems, or to avoid the "feature". Conceptual problems
> include enforced independence of types leading to combinatorial explosion
> of classes, inability to specify solutions for particular types or type
> combinations, inability to inherit, and more.
> Solution: ?
problems you list are worse than others. For instance, it would be nice
to be able to specify solutions for particular types, but the inability
to inherit from specialized classes is a much bigger issue.
Also, as a library author you leave out the biggest issue that I've
faced with specialization: if I expose an interface that has a
specialized type parameter, I have to document this fact to my users,
who in turn have to remember to "specialize through" to the place where
the type param is provided. It's easy to mess this up, and there's
nothing (other than poor performance) to clue you in to what happened.
In my particular case, this has made working with a specialized Numeric
library much less attractive... because I'm forced to choose between
beautiful and fast code.
Have you looked at https://github.com/azavea/numeric? After a bunch of
> (5) Generic numeric code is awkward and low-performance.
> Solution: specialize.
> Problem: see (1).
testing and profiling it gets good performance (e.g. comparable to
direct code) under Scala 2.9. I'd be interested in your comments or
critiques.
Tom Switzer and I are working on a series of proposals dealing with
Numeric (as well as numbers and math in Scala more generally).
Hopefully we'll be able to share that soon.
-- Erik
Thanks for the feedback! The library as it stands is intended as a
jumping off point and a proof of what is possible... I certainly don't
think the names are necessarily final.
On Tue, Nov 15, 2011 at 02:17:04PM -0500, Rex Kerr wrote:
> Bad:
> <=> looks way too much like the Pascal <> "not equal" operator and the
> scalaz <===> "distance" operator.
I forget which language I used this compare operator in, but it could
easily be removed. Are there any other good/canonical operators for
cmp(lhs, rhs): Int?
> ** looks like a collection operator--multiplication on matricies or
> somesuch (although I understand the historical attraction). Use something
> that doesn't conflict with the "doubling means over a collection" meme.
> Also, this has the same precedence as *, which yields surprising results.
> Use something with higher precedence like \^ or ~^.
Good suggestion. I would probably use something like ~^ just because \^
might suggest division.
> Not sure:
> !== does not match the scalaz /== (and I have also seen =/=), but as long
> as it is typesafe, that's fine. I think scalaz made the wrong (i.e.
> inconsistent) choice on that one; as long as people coordinate on !== vs.
> =/= for "typesafe not-equals" I think it's fine.
I got this from things like Javascript (where === and !== are used in
preference of the "normal" != and !==). I'm amendable to changing these
if necessary (that goes for all the operator choices) although I agree
that /== seems less used than either !== or =/=.
> Good:
> === should be defined everywhere. Typesafe equals is great. (I think
> this is typesafe...if not, this is the worst possible choice of symbol.)
Yes, this (and !==) are type safe, with the caveat that the library
obviously does some Numeric conversions to support literals. So you can
say things like:
def isZero[T:Numeric](t:T) = t === 0
Which is only type safe due to conversions from Int => T.
> The lack of extensibility is a bit problematic, but can't one just define
> the appropriate implicits?
I think that due to the way I built the ConvertableFrom/ConvertableTo
stuff things might not always work as expected. This would mostly mean
that things like:
def addRationalOne[T:Numeric](t:T) = t + Rational(1, 1)
...would not work (for some user-defined Rational class). So maybe it's
not a big deal.
-- Erik
Rex,
Thanks for the feedback! The library as it stands is intended as a
jumping off point and a proof of what is possible... I certainly don't
think the names are necessarily final.
I forget which language I used this compare operator in, but it could
On Tue, Nov 15, 2011 at 02:17:04PM -0500, Rex Kerr wrote:
> Bad:
> <=> looks way too much like the Pascal <> "not equal" operator and the
> scalaz <===> "distance" operator.
easily be removed. Are there any other good/canonical operators for
cmp(lhs, rhs): Int?
After yet another "Scala is too complex" discussion, it occurred to me that, although these discussions very often mistake novelty for complexity, or mistake the complexity of the problem with the complexity of the solution, there _are_ still a number of areas where Scala could benefit from simplification (i.e. the resulting code would be simpler, even if Scala as a language was more complex, since it would allow a simple solution to be used where a complex one now must be).
I'm ignoring the obvious style choice ones like "make Scala like Haskell".
I was curious what areas other people have identified, and what, if any, solutions they've identified? Or, perhaps people will take issue with some of mine?
My list includes:
(1) Specialization. The basic concept isn't too bad, but the implementation is so full of bugs and limitations that employing it in anything but the most straightforward cases is painful at best. I really don't know how to solve this except for serious manpower to be devoted to solving the problems, or to avoid the "feature". Conceptual problems include enforced independence of types leading to combinatorial explosion of classes, inability to specify solutions for particular types or type combinations, inability to inherit, and more.
Solution: ?
(2) No MyType. The intent behind
abstract class A {
def clone(): MyType
}
class B extends A {
def clone() = new B
}
is amazingly obvious, yet Scala provides no simple way to achieve this.
Solution: Yes, there are issues with contravariance (which causes issues with taking MyType as a parameter of a method), but at least the compiler could just bail out with an error instead of not even having the feature.
(3) Basic features (mostly FP) are missing from parts of the library, causing unnecessarily verbose and hard-to-follow code.
(a) It seems like "re-invent |>" is the answer to a StackOverflow question about once a week.
(b) Using `copy` to transform data is amazingly awkward: x.copy(_1 = x._1+1). How about x.modify1(_ + 1)?
(c) Option has no fold
(d) There is no default container for a single mutable variable.
... (this could go on for a while; I hope these are some of the most important)
Solution: just add these things to the library!
(4) Collections library has too many methods, and the ones it has are not consistently present by function:
(a) collect, collectFirst but no collectLast
(b) drop, dropRight, dropWhile, but no dropRightWhile
(c) filter/filterNot but no findNot, fornone, doesnotexist, etc.
(d) headOption but no lastOption
(e) reverseMap but no reverseFilter, reverseFlatMap, etc.
(f) minBy, maxBy but no sumBy, productBy
...(various others)
This leads to a large memorization burden.
Solution: more views, less duplication. Option view wraps any single-element return in an option. Reverse (or right) view does things from other end.
Open problem: how can this have acceptable performance?
(5) Generic numeric code is awkward and low-performance.
Solution: specialize.
Problem: see (1).
(6) "Enrich" my library pattern is awkward to write and often does a lot more than is needed (e.g. creates a class where an extension method would provide superior performance).
Solution: SIP for implicit classes may be enough. This requires escape analysis to work flawlessly, or for the compiler to provide inlining, or it only solves half the problem--but at least it (mostly) solves the complexity half, which is what we're after for the purposes of this discussion.
(7) Overloading method names by type is a feature that acts as a second-class citizen. The most notable offender is the restriction on default values in overloaded method names even when the two are still completely unambiguous. If I am to understand Martin's StackOverflow answer, this is because the _language specification_ would become more complex. But this is pushing the complexity from the language specification onto the programmer in the form of an extra rule ("only one overloaded method can have default parameters") instead of the obvious "ambiguity is not allowed".
Also, since ambiguity is presently allowed: class Z { def foo(s: String, a: Int = 42) = a; def foo(t: String) = t }, one has yet more to remember regarding how ties are actually resolved (shorter form is used; if return type is different, type inference can be used to get longer form; if parameters are named differently, referring to a parameter by name can be used to switch; otherwise if the default parameter is hidden there's no way to get it back).
Solution: allow anything unambiguous. Disallow anything ambiguous.
That's intentional. I believe |> is not idiomatic Scala style; and I would tend to use method chaining instead. Generally, trying to do point-free style in Scala is a dead end.
Is that subject an open ticket? I have a note somewhere that
foo.copy(x += 1)
should work, but I don't know if it's in the database.
(You're not getting "modify1" though, that is hardly
the road to lowering complexity.)
> (4) Collections library has too many methods, and the ones it has are not
> consistently present by function:
> (a) collect, collectFirst but no collectLast
No collectLast, really, this troubles you? "First" and "last" are not
exactly symmetric concepts. (What's the last element of Stream from 1?)
> (d) headOption but no lastOption
scala> Nil.lastOption
res0: Option[Nothing] = None
Such is the nature of masochism. *shrug* but be aware that there is a
social penalty to simplification. It's not free and you cannot ignore it.
--
Tony Morris
http://tmorris.net/
But this is pushing the complexity from the language specification onto the programmer in the form of an extra rule ("only one overloaded method can have default parameters") instead of the obvious "ambiguity is not allowed". Also, since ambiguity is presently allowed: class Z { def foo(s: String, a: Int = 42) = a; def foo(t: String) = t }, one has yet more to remember regarding how ties are actually resolved (shorter form is used; if return type is different, type inference can be used to get longer form; if parameters are named differently, referring to a parameter by name can be used to switch; otherwise if the default parameter is hidden there's no way to get it back).
Solution: allow anything unambiguous. Disallow anything ambiguous.
(8) Default parameter values and implicits don't free you from remembering that you actually have parameters there when you use functions. In particular,
def dup(s: String, n: Int = 2) = s*n
dup("fish") // "fishfish"
List("fish","cow").map(dup) // Error
On Tue, Nov 15, 2011 at 6:28 PM, Rex Kerr <ich...@gmail.com> wrote:
After yet another "Scala is too complex" discussion, it occurred to me that, although these discussions very often mistake novelty for complexity, or mistake the complexity of the problem with the complexity of the solution, there _are_ still a number of areas where Scala could benefit from simplification (i.e. the resulting code would be simpler, even if Scala as a language was more complex, since it would allow a simple solution to be used where a complex one now must be).
I'm ignoring the obvious style choice ones like "make Scala like Haskell".
I was curious what areas other people have identified, and what, if any, solutions they've identified? Or, perhaps people will take issue with some of mine?
My list includes:
(1) Specialization. The basic concept isn't too bad, but the implementation is so full of bugs and limitations that employing it in anything but the most straightforward cases is painful at best. I really don't know how to solve this except for serious manpower to be devoted to solving the problems, or to avoid the "feature". Conceptual problems include enforced independence of types leading to combinatorial explosion of classes, inability to specify solutions for particular types or type combinations, inability to inherit, and more.
Solution: ?Specialization will indeed take time to get right and push further. My advice for now would be: Treat it as a systems feature. Don't use @specialized in your code, but simply profit from the improved performance of specialized functions and tuples.
(2) No MyType. The intent behind
abstract class A {
def clone(): MyType
}
class B extends A {
def clone() = new B
}
is amazingly obvious, yet Scala provides no simple way to achieve this.
Solution: Yes, there are issues with contravariance (which causes issues with taking MyType as a parameter of a method), but at least the compiler could just bail out with an error instead of not even having the feature.
MyType has a lot of hidden complexities. All formal treatments of MyType have included "precise types", which are an anathema to OOP.
(3) Basic features (mostly FP) are missing from parts of the library, causing unnecessarily verbose and hard-to-follow code.
(a) It seems like "re-invent |>" is the answer to a StackOverflow question about once a week.
That's intentional. I believe |> is not idiomatic Scala style; and I would tend to use method chaining instead. Generally, trying to do point-free style in Scala is a dead end.
(b) Using `copy` to transform data is amazingly awkward: x.copy(_1 = x._1+1). How about x.modify1(_ + 1)?
(c) Option has no fold
(d) There is no default container for a single mutable variable.
... (this could go on for a while; I hope these are some of the most important)
Solution: just add these things to the library!Maybe. It amounts to having a larger library. Not saying this is bad, but it is hard to argue that this will reduce complexity.
(4) Collections library has too many methods, and the ones it has are not consistently present by function:
(a) collect, collectFirst but no collectLast
(b) drop, dropRight, dropWhile, but no dropRightWhile
(c) filter/filterNot but no findNot, fornone, doesnotexist, etc.
(d) headOption but no lastOption
(e) reverseMap but no reverseFilter, reverseFlatMap, etc.
(f) minBy, maxBy but no sumBy, productBy
...(various others)
This leads to a large memorization burden.
Solution: more views, less duplication. Option view wraps any single-element return in an option. Reverse (or right) view does things from other end.
Open problem: how can this have acceptable performance?And code size. Yes. I'd be interested in seeing solutions to this one.Or maybe decide to drop some methods from collections instead of adding new ones?
(5) Generic numeric code is awkward and low-performance.
Solution: specialize.
Problem: see (1).
(6) "Enrich" my library pattern is awkward to write and often does a lot more than is needed (e.g. creates a class where an extension method would provide superior performance).
Solution: SIP for implicit classes may be enough. This requires escape analysis to work flawlessly, or for the compiler to provide inlining, or it only solves half the problem--but at least it (mostly) solves the complexity half, which is what we're after for the purposes of this discussion.
(7) Overloading method names by type is a feature that acts as a second-class citizen. The most notable offender is the restriction on default values in overloaded method names even when the two are still completely unambiguous. If I am to understand Martin's StackOverflow answer, this is because the _language specification_ would become more complex. But this is pushing the complexity from the language specification onto the programmer in the form of an extra rule ("only one overloaded method can have default parameters") instead of the obvious "ambiguity is not allowed".
Also, since ambiguity is presently allowed: class Z { def foo(s: String, a: Int = 42) = a; def foo(t: String) = t }, one has yet more to remember regarding how ties are actually resolved (shorter form is used; if return type is different, type inference can be used to get longer form; if parameters are named differently, referring to a parameter by name can be used to switch; otherwise if the default parameter is hidden there's no way to get it back).
Solution: allow anything unambiguous. Disallow anything ambiguous.
That looks like a play with words to me. You have to specify (including all corner cases!) what ambiguous means. That specification is hard enough for plain overloading. Adding default arguments into the mix risks making it totally incomprehensible.
On Tue, Nov 15, 2011 at 3:20 PM, martin odersky <martin....@epfl.ch> wrote:On Tue, Nov 15, 2011 at 6:28 PM, Rex Kerr <ich...@gmail.com> wrote:
After yet another "Scala is too complex" discussion, it occurred to me that, although these discussions very often mistake novelty for complexity, or mistake the complexity of the problem with the complexity of the solution, there _are_ still a number of areas where Scala could benefit from simplification (i.e. the resulting code would be simpler, even if Scala as a language was more complex, since it would allow a simple solution to be used where a complex one now must be).
I'm ignoring the obvious style choice ones like "make Scala like Haskell".
I was curious what areas other people have identified, and what, if any, solutions they've identified? Or, perhaps people will take issue with some of mine?
My list includes:
(1) Specialization. The basic concept isn't too bad, but the implementation is so full of bugs and limitations that employing it in anything but the most straightforward cases is painful at best. I really don't know how to solve this except for serious manpower to be devoted to solving the problems, or to avoid the "feature". Conceptual problems include enforced independence of types leading to combinatorial explosion of classes, inability to specify solutions for particular types or type combinations, inability to inherit, and more.
Solution: ?Specialization will indeed take time to get right and push further. My advice for now would be: Treat it as a systems feature. Don't use @specialized in your code, but simply profit from the improved performance of specialized functions and tuples.
For those of us trying to create high-performance code now, this is of small comfort. But, granted, newcomers to Scala need pay no attention to the man behind the curtain.
(2) No MyType. The intent behind
abstract class A {
def clone(): MyType
}
class B extends A {
def clone() = new B
}
is amazingly obvious, yet Scala provides no simple way to achieve this.
Solution: Yes, there are issues with contravariance (which causes issues with taking MyType as a parameter of a method), but at least the compiler could just bail out with an error instead of not even having the feature.
MyType has a lot of hidden complexities. All formal treatments of MyType have included "precise types", which are an anathema to OOP.
If MyType cannot be in contravariant position, and generation of MyType is acyclic terminating in a method that is declared in the current class, then I don't think there can be a problem. Detecting this will require a bit of tooling, but I don't think it's conceptually very tricky to get something MyTypelike (i.e. that will solve the currently difficult typing problems with something simple that does what you want 90% of the time).
(3) Basic features (mostly FP) are missing from parts of the library, causing unnecessarily verbose and hard-to-follow code.
(a) It seems like "re-invent |>" is the answer to a StackOverflow question about once a week.
That's intentional. I believe |> is not idiomatic Scala style; and I would tend to use method chaining instead. Generally, trying to do point-free style in Scala is a dead end.
I don't use |> for point-free sytle; I use it when I need to refer to the same thing multiple times.
Example:
{ val temp = list.map(_ < 5).filter(f); temp ::: temp.reverse }
list.map(_ < 5).filter(f) |> { xs => xs ::: xs.reverse }
Named arguments are harder than default arguments, and they're already not checked at definition time, leading to stuff like:
That looks like a play with words to me. You have to specify (including all corner cases!) what ambiguous means. That specification is hard enough for plain overloading. Adding default arguments into the mix risks making it totally incomprehensible.
object Oops {
def f(i: Int, j: String) = j*i
def f(j: String, i: Int) = i.toString*j.length
}
scala> Oops.f(i = 5, j = "Hi")
<console>:10: error: ambiguous reference to overloaded definition,
both method f in object Oops of type (j: String, i: Int)String
and method f in object Oops of type (i: Int, j: String)String
match argument types (i: Int,j: java.lang.String)
Oops.f(i=5, j="Hi")
Where?
On Tue, Nov 15, 2011 at 9:28 AM, Rex Kerr <ich...@gmail.com> wrote:
I don't think it's OOP that is the problem. You assume parameter ordering in you argument.
If we assume indexed parameters it's conceivable that point free composition would work by matching names instead.
Or some other mechanism to declare roles besides order.
BR,
John
this.type example
http://scalada.blogspot.com/2008/02/thistype-for-chaining-method-calls.html
but that's actually the singleton type of the object. So you use
abstract type members:
http://code.google.com/p/scala-scales/wiki/VirtualConstructorPreSIP
and you pick when to allow a set type. The problem is the boilerplate
for providing both a class / trait that can be extended and one that
is directly usable.
You can ignore the rest of that link though, until code generation in
a Scala plugin is truly possible that is (within the same compilation
unit - you can generate outside it simply enough).
Thing is the plugin of Gerolfs might be more what one is after anyway
(except for the currently necessary separate compilation units) if you
don't also require extensibility:
https://github.com/gseitz/Lensed
A combo of the two would be lovely, ie. allow fluent immutable but
parameterised classes but also allow functions that capture those
transformations. I can dream ----
@Kevin - my salivation is starting to dry up waiting for your solution ^_^
I don't think it's OOP that is the problem. You assume parameter ordering in you argument.
<=> looks way too much like the Pascal <> "not equal" operator and the scalaz <===> "distance" operator.Bad:
** looks like a collection operator--multiplication on matricies or somesuch (although I understand the historical attraction). Use something that doesn't conflict with the "doubling means over a collection" meme. Also, this has the same precedence as *, which yields surprising results. Use something with higher precedence like \^ or ~^.
Not sure:
!== does not match the scalaz /== (and I have also seen =/=), but as long as it is typesafe, that's fine. I think scalaz made the wrong (i.e. inconsistent) choice on that one; as long as people coordinate on !== vs. =/= for "typesafe not-equals" I think it's fine.
Good:
=== should be defined everywhere. Typesafe equals is great. (I think this is typesafe...if not, this is the worst possible choice of symbol.)
The lack of extensibility is a bit problematic, but can't one just define the appropriate implicits?
(3) Basic features (mostly FP) are missing from parts of the library, causing unnecessarily verbose and hard-to-follow code.
(a) It seems like "re-invent |>" is the answer to a StackOverflow question about once a week.
That's intentional. I believe |> is not idiomatic Scala style; and I would tend to use method chaining instead. Generally, trying to do point-free style in Scala is a dead end.
I don't use |> for point-free sytle; I use it when I need to refer to the same thing multiple times.
Example:
{ val temp = list.map(_ < 5).filter(f); temp ::: temp.reverse }
list.map(_ < 5).filter(f) |> { xs => xs ::: xs.reverse }In fact, I think that shows precisely what's wrong with |>! I have noted a tendency among the more functional Scala users to avoid local val definitions at all cost. This is wrong! Good functional programming uses lots of meaningful names for every intermediate result. I personally think your first line without |> is much clearer than the second one.You can do even better by picking meaningful names and avoiding unnecessary punctuation:val firstHalf = list map f filter (_ < 5)firstHalf ++ firstHalf.reverseDo you see the difference in style? Reduced punctuation, increased meaning. Every closure, every special symbol has a cost in complexity. Where you can, avoid it!
On Tue, Nov 15, 2011 at 10:29 PM, Rex Kerr <ich...@gmail.com> wrote:For those of us trying to create high-performance code now, this is of small comfort. But, granted, newcomers to Scala need pay no attention to the man behind the curtain.
Yes. But note that you already get much more than Java provides.Help is welcome. Specialization is very very hard, that's why no other language has it.
If MyType cannot be in contravariant position, and generation of MyType is acyclic terminating in a method that is declared in the current class, then I don't think there can be a problem. Detecting this will require a bit of tooling, but I don't think it's conceptually very tricky to get something MyTypelike (i.e. that will solve the currently difficult typing problems with something simple that does what you want 90% of the time).
MyType only in covariant position is (a) quite restrictive and (b) requires complicated rules. Furthermore, it's not always what you want. For instance, you might have an abstraction with several implementation classes, but you do not want for MyType to specialize further in the implementation classes. In the end you get very limited usefulness for considerably complexity. Scala's abstract types are more general because they can be used anywhere, and add very little extra cost in notation.
I don't use |> for point-free sytle; I use it when I need to refer to the same thing multiple times.
Example:
{ val temp = list.map(_ < 5).filter(f); temp ::: temp.reverse }
list.map(_ < 5).filter(f) |> { xs => xs ::: xs.reverse }In fact, I think that shows precisely what's wrong with |>! I have noted a tendency among the more functional Scala users to avoid local val definitions at all cost. This is wrong! Good functional programming uses lots of meaningful names for every intermediate result. I personally think your first line without |> is much clearer than the second one.
You can do even better by picking meaningful names and avoiding unnecessary punctuation:val firstHalf = list map f filter (_ < 5)firstHalf ++ firstHalf.reverseDo you see the difference in style? Reduced punctuation, increased meaning. Every closure, every special symbol has a cost in complexity. Where you can, avoid it!
Named arguments are harder than default arguments, and they're already not checked at definition time, leading to stuff like:
object Oops {
def f(i: Int, j: String) = j*i
def f(j: String, i: Int) = i.toString*j.length
}
scala> Oops.f(i = 5, j = "Hi")
<console>:10: error: ambiguous reference to overloaded definition,
both method f in object Oops of type (j: String, i: Int)String
and method f in object Oops of type (i: Int, j: String)String
match argument types (i: Int,j: java.lang.String)
Oops.f(i=5, j="Hi")That's not a problem, because the two f's can still be distinguished by passing arguments positionally. So they should not be thrown out at definition time.
Some feedback on the code & ideas in your repo:
1. The speedups from Numeric specialisation are really welcome. I have
the feeling thats the most widely accepted part of your proposal.
After all, most people using or evaluating Numeric would/will need
non-boxed math to make serious use of Numeric.
2. The other changes, those not strictly required to specialise
Numeric, are more controversial and less backwards compatible; new
operators, incompatible changing the hierarchy, the conversion
framework. I don't really have use cases that require these
personally. Some of them seem to conflict with my aspirations to make
Numeric operations work for a broader range of data types, typically
non-scalars like Intervals and Vectors. Features like typesafe
equality don't seem relevant only to numbers in particular, and
overlap other libraries domains.
If you could "unbundle" these changes into smaller, more focused
change sets, it might make them easier to standardize, especially the
"low hanging fruit" of Numeric specialisation.
-Ben
On Tue, Nov 15, 2011 at 17:17, Rex Kerr <ich...@gmail.com> wrote:<=> looks way too much like the Pascal <> "not equal" operator and the scalaz <===> "distance" operator.Bad:
** looks like a collection operator--multiplication on matricies or somesuch (although I understand the historical attraction). Use something that doesn't conflict with the "doubling means over a collection" meme. Also, this has the same precedence as *, which yields surprising results. Use something with higher precedence like \^ or ~^.Well, that's their problem for having something that looks like Perl's compare, isn't it? :-)
Not sure:
!== does not match the scalaz /== (and I have also seen =/=), but as long as it is typesafe, that's fine. I think scalaz made the wrong (i.e. inconsistent) choice on that one; as long as people coordinate on !== vs. =/= for "typesafe not-equals" I think it's fine./= is Haskell. I can understand why Scalaz would pick it, but I think it is the wrong choice for Scala.Good:
=== should be defined everywhere. Typesafe equals is great. (I think this is typesafe...if not, this is the worst possible choice of symbol.)
The lack of extensibility is a bit problematic, but can't one just define the appropriate implicits?Odersky once actually proposed giving up on having == be based on equals, and, instead, have it based on an Eq[-T], with a fallback to equals, iirc. That is one proposal I wish had gone forward...
As I tried to convey before: the first implementation of default arguments supported overloads. The reason we disabledit was the non-deterministic naming scheme. The added spec complexity might have supported the decision, but it wasnot the primary reason.
Or do you mean the (hidden) names of the default values, which would then have to be shared across several methods? I have a hard time envisioning a scenario where the compiler would become confused about which variables to use.
I guess I'm not seeing the issue.
On Wed, Nov 16, 2011 at 4:48 PM, Rex Kerr <ich...@gmail.com> wrote:Or do you mean the (hidden) names of the default values, which would then have to be shared across several methods? I have a hard time envisioning a scenario where the compiler would become confused about which variables to use.
I guess I'm not seeing the issue.Yep, that's what Lukas was referring to. I suppose that for binary compatbility, you should be able to add a second overloaded method with defaults without requiring a recompile of the clients of the existing method.
You could encode the signature (perhaps just the erased parameter types) of the method into the names of the default getters. That would have a nice C++ feel to it!
Those getters are also virtual, so you have to make sure that sub-classes end up with the same names.
You could encode the signature (perhaps just the erased parameter types) of the method into the names of the default getters. That would have a nice C++ feel to it!
Just because C++ does something doesn't mean it's a bad idea. If you want power and transparency, you _must_ do something like that. The current choice in Scala sacrifices apparent simplicity and power in the language for easier implementation in the compiler. In the short run, that's understandable: there is limited manpower that can be devoted to the compiler, so it makes sense to shift some of the burden onto the users. But I don't think this should be the long-term goal.
If you want the capability and are willing to sacrifice a little bit of transparency, allow an optional annotation for a string added to the method names. Clunky? A bit. As clunky as not even having the functionality? I doubt it.
Alternatively, encode the parameter names. Then you state: if you want binary compatibility, you have to use different names for your default parameters. So for a parameter called "index", instead of f$default$1 you would have f$default$index if there was no clash on the name between methods, and f$default$index$1 and f$default$index$2 if there was. If the type was the same _and_ the default value was the same, then multiple methods could share f$default$index. This naming scheme would also make Java interop a bit simpler. (I tend to know that something is called "index" before I realize it is the second argument with a default.)
Although I try to avoid doubling up method names when possible, sometimes you have _exactly the same operation conceptually_ and therefore using multiple names distracts from what is going on. I not infrequently have things like
def display(s: String, columns: Int = 80) { ... }
def display(f: File, columns: Int) { ... }
def display(f: File) { display(f,80) }
def display(mc: MyClass, columns: Int ) { ... }
def display(mc: MyClass) { display(mc,80) }
...
Naming things displayString, displayFile really does not help understanding at all here. Implicitly converting everything to a Displayable (so you can have one display(d: Displayable, columns: Int = 80)) is even more work (though I have taken that approach also at times, and will do so more when implicit classes are implemented).
Those getters are also virtual, so you have to make sure that sub-classes end up with the same names.
Use whatever method that is currently used to make sure the sub-classes know how to get the default parameters at all. Solving already-solved problems is easy.
Anyway, I agree that solving this will not magically make all complaints about the complexity of Scala go away.
On Wed, Nov 16, 2011 at 5:53 PM, Rex Kerr <ich...@gmail.com> wrote:Although I try to avoid doubling up method names when possible, sometimes you have _exactly the same operation conceptually_ and therefore using multiple names distracts from what is going on. I not infrequently have things like
def display(s: String, columns: Int = 80) { ... }
def display(f: File, columns: Int) { ... }
def display(f: File) { display(f,80) }
def display(mc: MyClass, columns: Int ) { ... }
def display(mc: MyClass) { display(mc,80) }
...
Naming things displayString, displayFile really does not help understanding at all here. Implicitly converting everything to a Displayable (so you can have one display(d: Displayable, columns: Int = 80)) is even more work (though I have taken that approach also at times, and will do so more when implicit classes are implemented).
Giving them different names helps clients to understand compiler error messages, helps argument type inference, allows, eta expansion, and bunch of other little benefits.
But when you want to give a single name, I think the small amount of boilerplate involved with implicits is worth it. I would prefer an implicit parameter to a view.def display[D: Display](d: D)
This is getting off-topic, but it's still interesting, so:
Giving them different names helps clients to understand compiler error messages, helps argument type inference, allows, eta expansion, and bunch of other little benefits.
Given the limitations of the JVM stack trace (i.e. it tells you method names but not which method when arguments are overloaded), I agree with the first one.
The other two seem again like shifting work from the compiler-writer to the programmer. Type inference? It's just a mildly larger search space. So search it. Eta expansion? Static typing to the rescue: the types are not the same.
But when you want to give a single name, I think the small amount of boilerplate involved with implicits is worth it. I would prefer an implicit parameter to a view.def display[D: Display](d: D)
What benefit do you gain here? Well--okay, I can see one now, but what benefit would one gain if SI-5191 were fixed? (Or is that the issue?)
P.S. I think the MyType and collections methods solutions _might_ make a significant dent in the perceived complexity of Scala. Of course there's the initial shock value of (xs, ys).zipped.map(_ + 2*_) that is hard to offset (hopefully followed by wonder at how much has happened with so little code), but things like
def +: [B >: A, That] (elem: B)(implicit bf: CanBuildFrom[ArraySeq[A], B, That]): That
are kind of imposing. Even
def +: [B >: A] (elem: B): this.class[B]
is scary enough, what with the weird >: and the backwards-acting +: (where this.class is MyType, of course). Part of the problem is that it's not obvious what That is--this lends great power, but for all anyone reading the signature knows, That could be a Function0. I'm not sure we're willing to give up the extra power, though. I do quite like the string interoperability, for instance.
On Wed, Nov 16, 2011 at 7:17 PM, Rex Kerr <ich...@gmail.com> wrote:This is getting off-topic, but it's still interesting, so:In -debate? Never!Giving them different names helps clients to understand compiler error messages, helps argument type inference, allows, eta expansion, and bunch of other little benefits.
Given the limitations of the JVM stack trace (i.e. it tells you method names but not which method when arguments are overloaded), I agree with the first one.The stack trace gives you the line number, so with some digging you get the particular method. I'm more worried abou the scalac error of "could not apply arguments of types ... to <one of these six methods>". Sure, it's just like Java, other than for the uncertainty caused by type inference at, or prior to, the call site.
The other two seem again like shifting work from the compiler-writer to the programmer. Type inference? It's just a mildly larger search space. So search it. Eta expansion? Static typing to the rescue: the types are not the same.
I don't agree that complexity for the implementor/spec-writer necessarily buys simplicity for the programmer in this case. Change a signature; break argument or eta-expansion inference in an unrelated area of your program; try to clean up. (Admittedly, this can occur today when adding an overloaded method, but given that I'm not cheerleading static overloading in the first place, I'm not going to weigh up the relative suckiness of the two problems.)
But when you want to give a single name, I think the small amount of boilerplate involved with implicits is worth it. I would prefer an implicit parameter to a view.def display[D: Display](d: D)What benefit do you gain here? Well--okay, I can see one now, but what benefit would one gain if SI-5191 were fixed? (Or is that the issue?)
There are a few ways to express this:1. def display(a: String)2. def display(a: Displayable)3. def display[A <% Displayable](a: A)4. def display[A: Display](a: A)#1 violates the sacred Rule of Ortiz -- you ask your caller to implicitly an argument to a `String`, perhaps by providing a suite of conversion methods, hurting type safety in that scope in other places where `String` is used. Not what you suggested, but I thought I'd place it on the table before saying it was off the table.
#2 is better, as the conversion is to a dedicated type. But the conversion will be applied to the argument *before* it arrives in your method.
#3 is pretty much like #2, but you would also be able to accept, say, `List[A]` and convert each element of the collection to `Displayable`.
#4 means you don't actually need to allocate a `Displayable` wrapper; instead you just pass in the `Display` instance, to which you can pass the `A`. Furthermore, you can write implicits to provide, say, `Displayable[(A, List[A]))]`, given a `Displayable[A]`. Relying on the caller to invoke an implicit view, or requiring a view bound, are a bit clunkier when you get to that.
P.S. I think the MyType and collections methods solutions _might_ make a significant dent in the perceived complexity of Scala. Of course there's the initial shock value of (xs, ys).zipped.map(_ + 2*_) that is hard to offset (hopefully followed by wonder at how much has happened with so little code), but things like
def +: [B >: A, That] (elem: B)(implicit bf: CanBuildFrom[ArraySeq[A], B, That]): That
are kind of imposing. Even
def +: [B >: A] (elem: B): this.class[B]
is scary enough, what with the weird >: and the backwards-acting +: (where this.class is MyType, of course). Part of the problem is that it's not obvious what That is--this lends great power, but for all anyone reading the signature knows, That could be a Function0. I'm not sure we're willing to give up the extra power, though. I do quite like the string interoperability, for instance.
I too would prefer it if there was a simple version of `map`/`flatMap` implemented by the silent majority of the collections that handle arbitrary element types. But this could be encoded with an abstract type, rather than with `MyType`, with only a small cost to the implementor
Can it? In that case, every parameter would need non-intersecting LUBs
and GLBs. Take, for example:
def f(x: String, y: Int = 0) = x
def f(x: String, y: Double = 0.0) = x
Though they are not ambiguous at the declaration site, f("abc") is
ambiguous. Or, consider something even worse:
def f[T1 >: Nothing <: String](x: T1, y: Int = 0) = x
def f[T2 >: Null <: AnyRef](x: T2, y: Double = 0.0) = x
T1 is not more specific than T2, and T2 is not more specific than T1
-- T1 could be Nothing, while T2 could be AnyRef. Yet, both can be
String.
It seems to me that the only way to guarantee non-ambiguity at the
declaration site is to demand that all types are fully disjunct.
On Thu, Nov 17, 2011 at 02:54, Rex Kerr <ich...@gmail.com> wrote:Can it? In that case, every parameter would need non-intersecting LUBs
>
> I really think this is a non-issue. If you have an actual ambiguity, the
> code should catch it _at the declaration site_. If you do not have an
and GLBs. Take, for example:
def f(x: String, y: Int = 0) = x
def f(x: String, y: Double = 0.0) = x
Though they are not ambiguous at the declaration site,
f("abc") is
ambiguous. Or, consider something even worse:
def f[T1 >: Nothing <: String](x: T1, y: Int = 0) = x
def f[T2 >: Null <: AnyRef](x: T2, y: Double = 0.0) = x
T1 is not more specific than T2, and T2 is not more specific than T1
-- T1 could be Nothing, while T2 could be AnyRef. Yet, both can be
String.
It seems to me that the only way to guarantee non-ambiguity at the
declaration site is to demand that all types are fully disjunct.
It can't, even without introducing defaults. With defaults the
ambiguity can only grow.
class C {
protected def f(x: String) = 1
}
object Foo extends C {
def f(x: AnyRef) = 2
}
Is f("abc") ambiguous? From inside Foo yes, from anywhere else no.
scala> Foo.f("abc")
res0: Int = 2
On Fri, Nov 18, 2011 at 12:38 PM, Paul Phillips <pa...@improving.org> wrote:
On Wed, Nov 16, 2011 at 8:54 PM, Rex Kerr <ich...@gmail.com> wrote:It can't, even without introducing defaults. With defaults the
> I really think this is a non-issue. If you have an actual ambiguity, the
> code should catch it _at the declaration site_.
ambiguity can only grow.
class C {
protected def f(x: String) = 1
}
object Foo extends C {
def f(x: AnyRef) = 2
}
Is f("abc") ambiguous? From inside Foo yes, from anywhere else no.
This isn't ambiguous because String is more specific than AnyRef, and the rule is (or the proposed rule would be) to use the most specific type signature that matches.
In addition to "that's not the rule" and "the rule takes into account
both receiver and arguments", the rule takes into account inheritance.
String is more specific than AnyRef, but a method defined in a
subclass is more specific than one defined in a superclass. Plenty of
code depends on it.
On Fri, Nov 18, 2011 at 8:13 PM, Rex Kerr <ich...@gmail.com> wrote:On Fri, Nov 18, 2011 at 12:38 PM, Paul Phillips <pa...@improving.org> wrote:
On Wed, Nov 16, 2011 at 8:54 PM, Rex Kerr <ich...@gmail.com> wrote:It can't, even without introducing defaults. With defaults the
> I really think this is a non-issue. If you have an actual ambiguity, the
> code should catch it _at the declaration site_.
ambiguity can only grow.
class C {
protected def f(x: String) = 1
}
object Foo extends C {
def f(x: AnyRef) = 2
}
Is f("abc") ambiguous? From inside Foo yes, from anywhere else no.
This isn't ambiguous because String is more specific than AnyRef, and the rule is (or the proposed rule would be) to use the most specific type signature that matches.
That's not what the rule is. The rule takes into account both the receiver and the arguments. If you change that,
lots of code will break including the whole collections library.
But that was only one example why ambiguity of overload resolution cannot be resolved at the declaration site. There are many others. Here's another example:
class C[T] {
def foo(x: T)
def foo(x: String)
}
val x: C[String]
x.foo
You mean, pick the foo(String) because the erasure of the foo(T) is foo(Object)? What about that one then:def foo(x: Throwable)
class C[T] {
def foo(x: T)
}
val x: C[Exception]
x.foo
? If you still say that the foo(x: Throwable) should be picked you are in direct contradiction with the ways overloading resolution is done in Java and in Scala.
(a) It seems like "re-invent |>" is the answer to a StackOverflow question about once a week.
I wondered the very same thing. The difference between `x match { ... }`
and `{ ... } apply x` is that they generate different byte codes. The
former doesn't actually produce an instance of `PartialFunction` (where
the pattern match code has to be duplicated for `isDefinedAt`).
cheersadriaan
I don't like the whole concept of specialization made by the static
compiler. This feels sooo C++, I mean, an ugly hack, useful for 1% of
cases, but with lots of sharp edges. The right place for specialization
is in the JVM - it has all the information about which types are
actually used so JVM's HotSpot could do that much better. I'd also like
to see some day a good support for building small concrete immutables
e.g. for representing complex numbers - which could also benefit from
specialization and object inlining. Again - this is JVM's task, not
Scalac. So the solution would be to lobby Oracle for appropriate JVM
improvements. Or at least to move the effort to OpenJDK, to create a
working prototype for that feature and then make that included into
official JDK.
BTW: Type based JVM level code specialization can also help inlining of
virtual calls from megamorhic call sites - and these are very frequent
in Scala.
As for the rest of your points, I agree completely and I add one minor
thing:
Generic type inference in pattern matching should be more consistent
with the generic type inference in other parts of the language.
class A
case class B[T](param: T)
val a = new A
val b = B(a) // infers B[A], ok
Now let's try similar thing with pattern matching:
def test(obj: AnyRef): B[A] = {
obj match {
case b @ B(a: A) => b
}
}
<console>:12: error: type mismatch;
found : B[Any]
required: B[A]
Note: Any >: A, but class B is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
case b @ B(a: A) => b
^
To fix this I have to add ugly "asInstanceOf[]" suggesting something is
not statically type-safe, although it perfectly is. Things like this
make Scala feel complex, although it need not to be.
Anyway, probably the whole "type inference" part of the language is a
source of many surprises. I don't know if much can be done about this,
though.
Regards,
Piotr
W dniu 2011-11-15 18:28, Rex Kerr pisze:I don't like the whole concept of specialization made by the static compiler. This feels sooo C++, I mean, an ugly hack, useful for 1% of cases, but with lots of sharp edges. The right place for specialization is in the JVM - it has all the information about which types are actually used so JVM's HotSpot could do that much better.
After yet another "Scala is too complex" discussion, it occurred to me
that, although these discussions very often mistake novelty for
complexity, or mistake the complexity of the problem with the complexity
of the solution, there _are_ still a number of areas where Scala could
benefit from simplification (i.e. the resulting code would be simpler,
even if Scala as a language was more complex, since it would allow a
simple solution to be used where a complex one now must be).
I'm ignoring the obvious style choice ones like "make Scala like Haskell".
I was curious what areas other people have identified, and what, if any,
solutions they've identified? Or, perhaps people will take issue with
some of mine?
My list includes:
(1) Specialization. The basic concept isn't too bad, but the
implementation is so full of bugs and limitations that employing it in
anything but the most straightforward cases is painful at best. I
really don't know how to solve this except for serious manpower to be
devoted to solving the problems, or to avoid the "feature". Conceptual
problems include enforced independence of types leading to combinatorial
explosion of classes, inability to specify solutions for particular
types or type combinations, inability to inherit, and more.
Solution: ?
Yes, but I don't think it is a problem.
Whole lot of optimisations in JVM are speculative and do not rely on the
type system. Just detect tight loops where lots of boxing occurs and
specialize from there, basing on type information gathered at runtime.
This should work e.g. in such a scenario:
trait Function {
def apply(x: Any): Any
}
class Duplicate extends Function {
def apply(x: Any): Any = x match {
case a: Int => a * 2
case a: Long => a * 2
case a: Double => a * 2
}
}
And now the calling code:
val function = new Duplicate
for (i <- 1 to 10000000)
function(someConst) // here the boxing occurs
Step 1: Detect the line where lots of boxing occurs
Step 2: Dynamically generate a specialized method apply for the type of
the argument observed - by the way eliminating all the match cases
except one.
Step 3: Patch the loop with the call to the newly generated code,
removing the boxing.
Regards,
Piotr
I thought of doing this a while ago and I keep buttonholing people
trying to get someone to give me a reason it won't work. (I usually
find out why something won't work the hard way, but in this case I
don't have the time if I can avoid it.) So far nobody has come up with
a reason. That you propose it independently makes it sound that much
more plausible.
I like the idea. A minor problem I can see is running in restricted
environments, where you cannot install your own classloaders, e.g. in
unsigned applets. There *are* people who do use Scala for unsigned
applets. So there would have to be at least some global compiler switch
to turn off the specialization at all, both in the compiled code and
Scala library (or a separate non-specialized Scala library jar).
Regards,
Piotr
It's definitely plausible. Eric Allen's NextGen implementation of
generics for Java did something similar: generating a template for the
generic class, then using a class loader to specialize the class on
demand. An unreleased version of X10 did something similar.
Nate
True. I was not thinking about Scala's method specialization. Both NextGen and X10 specialized generic classes on all type parameters. I don't recall what NextGen did, but the approach we implemented in X10 was to translate generic methods into generic inner classes, reducing the problem to class specialization. All of this never got out of a proof-of-concept phase, however. The main complication and the reason we abandoned the whole endeavor was that the X10 compiler was generating Java source, not bytecode and it was difficult generating code acceptable to javac. The plan was to revisit generics after completing the bytecode backend, but I left IBM before that happened and I'm not sure what they're doing now.
Nate
Alternatively, specialization could compile _not_ the actual classes used, but a _class bytecode generator_ which would on the first use in the code generate and load the appropriate specialized class (if done at runtime, or generate and compile the appropriate specialized class if done at compile time). That way you could just blithely write Function3[@specialize -T1, @specialized -T2, @specialized -T3, @specialized +R] and you wouldn't have to deal with 10k different classes in general; each program would get those classes that actually were used. This would be FAR less work than having the JVM try to do the equivalent without understanding what was going on.
--Rex
I'm certain that it would not work at all on Android :-)
--
paul.butcher->msgCount++
Snetterton, Castle Combe, Cadwell Park...
Who says I have a one track mind?
http://www.paulbutcher.com/
LinkedIn: http://www.linkedin.com/in/paulbutcher
MSN: pa...@paulbutcher.com
AIM: paulrabutcher
Skype: paulrabutcher
Well, ok, it wouldn't work on Android. But Scala *isn't* working on
Android right now because _static_ generation is resulting in way too
much code.
I think the idea of having a build step that generates it statically
for the closed set of usages that it can find might be the best way to
go, particularly used with SBT. Libraries could leave it open, and
then applications would "close" it and generate the required files.
Much like a link step, sadly.
--
Daniel C. Sobral
I travel to the future all the time.
Except for the fact that, for reasons which we still haven't (as far as I'm aware?) got to the bottom of, something changed between 2.8.x and 2.9.x which means that even after being proguarded, the Scala library is often too large to fit on an Android device. See this thread for a recent discussion of this issue:
https://groups.google.com/forum/#!topic/scala-debate/El3QZEbIwHg
> If you want to solve the named argument problem, that's tricky but
> relatively doable.
I have never understood why named arguments are not treated as part of
the method name (like smalltalk, perhaps prying out the 1st parameter
better than smalltalk does) e.g.
object . move from: aPlace to: anotherPlace by: aTime via: aRoute
(smalltalk would do object.moveFrom: ... to: ... by: .... via: ...)
method name = move:from:to:by:via:
param ordering can be treated as fixed, can even allow positional
invocation for those who won't have it otherwise:
object . move (aPlace, aPlace, aTime, aRoute)
Just an observation, I know there is zero chance of anything like this
happening.
I have never understood why named arguments are not treated as part of the method name (like smalltalk, perhaps prying out the 1st parameter better than smalltalk does) e.g.
>
> On Wed, Nov 30, 2011 at 9:10 AM, Sophie <itsm...@hotmail.com> wrote:
>>
>> I have never understood why named arguments are not treated as part of the
>> method name (like smalltalk, perhaps prying out the 1st parameter better
>> than smalltalk does) e.g.
>
> Probably becauset means that parameter names now become part of the API,
> so a simple renaming will break existing users.
On 2011-11-15 15:29:36 -0600, Rex Kerr <ich...@gmail.com> said:I have never understood why named arguments are not treated as part of the method name (like smalltalk, perhaps prying out the 1st parameter better than smalltalk does) e.g.
If you want to solve the named argument problem, that's tricky but
relatively doable.
object . move from: aPlace to: anotherPlace by: aTime via: aRoute
(smalltalk woucld do object.moveFrom: ... to: ... by: .... via: ...)
Exactly. Argument names are part of the API. Here's a fun one:val x : Int => Int = _ + 1x(v1 = 2) // Right, v1 become part of our official API in 2.8.x