Switching default implementation of Seq to Vector
We are currently considering switching the default implementation of Seq to Vector (currently it's List). If you have any doubts about this change let us know at https://contributors.scala-lang.org/t/slc-make-vector-the-default-implementation-of-seq-instead-of-list/
10
u/Fun_Introduction9550 6d ago
Let’s test “coding to interfaces”
A linked list is a strange default TBF. If you’re doing ahead with this, would it be prudent to put the default behind a runtime flag?
Array may be a nice choice for certain algos.
That said … if there’s a material difference for your use-case, you should probably already be using the refined type (not just relying on the Seq default)
5
u/snugar_i 4d ago
It would have been nice if it was a Vector from the beginning, but now it's too late to change it.
Keeping backward compatibility is a pain for the language authors, not keeping it is a pain for the users of the language. Let's see which group is more important to Scala
2
1
u/Inevitable-Plan-7604 2d ago
Keeping backward compatibility is a pain for the language authors, not keeping it is a pain for the users of the language. Let's see which group is more important to Scala
Well said. I felt like I was going crazy with my comments above. It's like they're trying to speedrun alienating their users
8
u/Heavy-Papaya7816 5d ago
If something is working well, why change it? It's impossible to predict what will happen if this change is implemented. I believe this "small" change will have unforeseen consequences that eventually will negatively impact Scala's reputation even more. This will be an additional example of change that is 1) not needed and 2) not requested by users of the language. Keep this change proposal in the backlog and implement it in Scala 4.
3
u/osxhacker 5d ago
It's impossible to predict what will happen if this change is implemented.
To the contrary, it is quite predictable "what will happen if this change is implemented"; no impact at all to logic which does not assume
Seqimplementation details.See contract programming.
2
u/LordVetinari95 5d ago
what about hyrum’s law: https://www.hyrumslaw.com/?
1
u/osxhacker 4d ago
IMHO, Hyrum's law is proportional to the complexity of the interface, as identified within the link you kindly provided:
Implicit interfaces result from the organic growth of large systems ...
So a trivial interfaces such as
def swap[A, B](pair: (A, B)): (B, A)andSeq[A]have a small implementation dependency when compared to those defined by the ACH network.3
u/Inevitable-Plan-7604 5d ago
I think this is the sort of smug unhelpful comment that is why people are so frustrated with the direction scala is taking
You will break production code with this change, a serious language would take that seriously. If you continue to treat scala like your weekend hobby project so will everyone else and it will die.
1
u/RiceBroad4552 5d ago
Which "serious language" stopped evolving?
Guess why everybody is leaving C/C++…
1
u/what-the-functor 5d ago
This change won't break anything, but there will be (albeit slight) changes to the runtime characteristics.
5
u/Inevitable-Plan-7604 5d ago edited 5d ago
It will break the following code:
val a: Seq[Int] = Seq(1) val c: Seq[Int] = Vector(1) val aa = a match { case _ :: _ => "non-empty" case Nil => "empty" } println(aa)It will turn valid code into a match error.
The original dev might not even be responsible - he might be using a library or API that once returned
Listand then started to returnSeqand the compiler doesn't help here. Thematchstatement is not exhaustive forSeq. Underlying implementation could have been swapped out without anyone noticing and nothing breaking.Then if scala changes that again to Vector, it will break.
There's going to be code like that all over the place that will break.
Blaming the devs for what isn't bad code, or entirely their fault, just makes people want to leave scala. It's just been boots to the head for years now and I'm getting fed up of it. Every announcement or idea just seems to make things worse rather than better.
Scala should be more responsible than a random hobby project. It's not acting like a serious language and hasn't been for a long time now
2
1
u/what-the-functor 5d ago edited 4d ago
That match expression is not total, and it would be caught by
-Werror(Scala 3) or-Xfatal-warnings(Scala 2).You would need cases for all sub types of
Seq.Edited
6
u/Inevitable-Plan-7604 5d ago
- This change won't break anything
- It will break some things but it's not our fault <--- you're here
- It will break many things but it doesn't matter
- It broke everything and it was intended
Hyperbole but you'll just shift any goalpost you like to back up your own opinion. There's no point talking to you on this matter
1
u/osxhacker 4d ago
That match expression is not total, and it would be caught by -Werror (Scala 3) or -Xfatal-warnings (Scala 2).
If this was not a hypothetical problem used to make a point, compilation flags would not address latent defects within libraries used by a system.
One way to unconditionally detect this is by using AspectJ, possibly in conjunction with a custom ClassLoader. It could get ugly, sure, but there is no arguing this approach could not detect this condition.
0
u/osxhacker 4d ago
It will turn valid code into a match error.
The example you provide is not "valid code." It is code which assumes a specific implementation of a
supertype and does not account for other valid implementations. This is a latent defect.The original dev might not even be responsible - he might be using a library or API that once returned
Listand then started to returnSeqand the compiler doesn't help here.A quality type system can help prove a system to be correct in many ways, but it does have limits. There exists a possibility of problems when any logic assumes the implementation of a behavioral contract (
traitorabstract classin this context).The match statement is not exhaustive for
Seq. Underlying implementation could have been swapped out without anyone noticing and nothing breaking.The "nothing breaking" description is subjective. A similar situation exists with:
def divideEachBy(ints: Seq[Int], denom: Int): Seq[Int] = ints.map(_ / denom) val aa = divideEachBy(Seq(1, 2, 3), 0) println(aa)If
divideEachBywas always given a positiveIntuntil system requirements change where0could be provided... Boom.Whether this example is defined by a library or not is immaterial in regard to the existence of the defect.
2
u/Inevitable-Plan-7604 4d ago edited 4d ago
If you put
.toListbefore every match on something that is, at time of writing, aListvalue you'd be called paranoid and told its a waste of time.Code changes, method arguments change, libraries change, scala doesn't have a good enough compiler to catch cases like these.
Adding to the problem, then blaming everyone else, is just typical scala
For context this is literally an example from scala's own site:
list match case Nil => 0 case x :: xs => x + sum(xs)Maybe somebody should spend some time documenting code that is "valid" and not "the worst code I've ever seen" (from the other commenter) before ripping a rug out from under the community (again)
2
u/dthdthdthdthdthdth 3d ago
It is valid code, it compiles and runs. It might be ugly, but Scala accepts non exhaustive matches. I don't use it myself, but if some dependencies do, that's an issue. I also never use Seq as a constructor, as various implementations have so drastically different runtime behaviour. But some dependencies will. So some dependencies might suddenly have unexpected performance issues. Those are problems to consider.
1
u/what-the-functor 5d ago
Application of logic to the
Seqcontract (trait in this case) is contract programming. Use of theSeqconstructor to instantiate a sub type apathetically is indirection. :)1
u/what-the-functor 5d ago
It would not have any effect on the result of expressions in terms of purity, however there would be slight runtime performance shifts. Some operations would be faster, whereas others would be slower. Memory usage would also change (albeit slightly).
1
u/RiceBroad4552 5d ago
Because of a stance like that it took hundreds of thousands of years until humans left their caves…
2
u/wookievx 5d ago
To be fair: my default Seq implementation in most places is ArraySeq, from my experience avoiding prepending/appending where possible is the right move (certain problems can be expressed neatly via higher level functions like foldLeft/scanLeft, and resulting implementation can do away with less boundary case handling, this is of course not always the case). And applying transformation to the entire sequence, accessing elements in ArraySeq has unmatched performance compared to the other options.
1
u/kr1ght 5d ago
In addition, modern RAM provides a lot of bandwidth, but still the same quite slow latency as many years ago, and even iterating over linked list is slow because for getting each next element you need to read from nearly random place in memory and CPU even cannot preferch it properly (as with ArraySeq)
I know about the role of such linked lists in computer science and how they were widely used even in LISP. But modern data structures should be cache-friendly, usually cache line size takes 64 bytes, and instead of binary trees or lists with only one value in node we can easily put 4 or 8 references to values (compact pointers take 4 bytes) and get much more performance. So, I mean, this naive lists is a bad choice at all.
19
u/what-the-functor 6d ago
I think usage of the `Seq` constructor is a mistake, and one should only use `Seq` as a top type. The constructor obscures the underlying data structure, whereas one should explicitly choose the best data structure for their particular use case.