In effect, the "elegance" of Scala here is really the product of its heavy GC dependence. So the comparison isn't as meaningful as it might seem.
let codes: Vec<HuffmanCode> = data_table.iter()
.zip(&code_lengths)
.zip(&code_table)
.map(|((&value, &length), &code)| {
HuffmanCode {
length: length,
code: code,
value: value,
}
})
.collect();It may be possible to implement `.zipped` on tuples though.
> (except the obvious `:Vec<_>` type that is not required)
It is required, otherwise, `collect()` doesn't know what type of collection to collect into. > It may be possible to implement `.zipped` on tuples though.
This is impossible without varargs, no?Nah, you'd just implement it as an impl over and over on tuples of reasonable size (up to 16 or so).
It wouldn't be elegant, but it'd work in practice.
Yes, unless there is some other mention of Vec, i.e. it is returned from the function.
> This is impossible without varargs, no?
Varargs would definitely help, and would allow Rust to borrow more ideas without workarounds. But one can do it manually for each tuple variant.
Not elegant, but it works.
My question is then: is there something fundamentally preventing Rust from achieving similar levels of expressiveness to this Scala example without incurring in unnecessary runtime overhead? Given that both languages are statically typed, my inclination would be to say no: the information is there, the compiler should be able to figure it out. But, alas, i know very little about these things, hence my question :)
Update: pcwalton gave some nice insight on why Rust needs some of these constructs on an uncle comment.
The `map` is uglier in Rust because
* the comparison was against a constructor with positional, rather than named, arguments
* the Scala code didn't need to dereference any arguments, and
* and Scala's `Zipped` is a special type with a special `map` function that takes three arguments, unlike a normal iterator.
The first and last points could be easily copied in Rust: you'd build a constructor for HuffmanCode and augment iterators of tuples with with a starmap method (that can be done in a library). The middle point can be done before the zipping. The result would be
let codes =
izip!(data_table.iter().cloned(), code_lengths, code_table)
.starmap(HuffmanCode::new)
.collect();
Rust's collect is never implicit, like Scala's CanBuildFrom. This prevents accidental collects, which helps writing fast code, but in principle I don't see why it couldn't be implicit - it would just require the whole standard library to be overhauled. let codes: Vec<_> =
izip!(&data_table, code_lengths, code_table)
.map(|(&value, length, code)| {
HuffmanCode {
length: length,
code: code,
value: value,
}
})
.collect();
(If HuffmanCode was a tuple type, this could even be #![feature(fn_traits)]
let codes: Vec<_> =
izip!(data_table.iter().cloned(), code_lengths, code_table)
.map(|args| (&HuffmanCode).call(args))
.collect();
but now I'm just playing around.)Core language features: both have algebraic datatypes like ML ("case classes" in Scala, enums in Rust), and a 'match' statement that makes working with them easy. Both have pervasive destructuring that works with match arms, 'let' bindings, etc.
Data structures and mutation: both encourage a pragmatic version of immutability -- use values and provide functional interfaces primarily, but support mutation where needed. In Rust there are 'Cell' and 'RefCell' types, just like ML's cell-based interior mutability.
Library idioms: all three have the standard set of functional-style higher-order transformation functions ('map', 'filter', etc. -- Rust in particular has a very rich Iterator trait API), and it's idiomatic to use these.
FWIW, Rust's first/bootstrap compiler was written in OCaml and there's a lot of obvious influence.
I don't want this to devolve into a No True Scotsman debate, I just feel that the parent post -- which decried a lack of similarity between Rust and Scala because they were both in the ML family -- was founded on a weak premise.
For me, type inference, strong typing, and ADTs with pattern matching puts a language into the ML family quite easily.