Open isaacabraham opened 3 years ago
That does look a lot closer, yes, since you're just setting up a non-eagerly evaluated structure that doesn't actually do anything until you try to do something with it. I think it's different on the back end since here it's making a seq
In the same manner, this part is wrong:
This is because calling these iterator methods without collecting them or assigning them to a variable just makes a big complex type; we haven't mapped or filtered anything yet. Let's see what it looks like by getting the compiler mad:
fn main() { let times_two_then_even: i32 = (0..=10) // Tell the compiler it's an i32 .map(|number| number * 2) .filter(|number| number % 2 == 0); }
The compiler complains:
expected type `i32` found struct `Filter<Map<RangeInclusive<{integer}>, [closure@src/main.rs:3:14: 3:33]>, [closure@src/main.rs:4:17: 4:41]>`
So all we've done is put together a type Filter<Map<RangeInclusive etc. etc. etc. And if we call .map a whole bunch of times it just keeps on putting this big struct together:
found struct `Map<Map<Map<Map<Map<Map<Map<Map<RangeInclusive<{integer}>, [closur e@src/main.rs:3:14: 3:33]>, [closure@src/main.rs:4:14: 4:33]>, [closure@src/main .rs:5:14: 5:33]>, [closure@src/main.rs:6:14: 6:33]>, [closure@src/main.rs:7:14: 7:33]>, [closure@src/main.rs:8:14: 8:33]>, [closure@src/main.rs:9:14: 9:33]>, [c losure@src/main.rs:10:14: 10:33]>`
This struct is all ready to go, and gets run once when we actually decide to do something with it (like collect it into a
Vec
). The pipeline operator in F#, however, gets run every time you use it: if you do something like this:let addOne x = x + 1; let number = addOne 9 |> addOne |> addOne |> addOne |> addOne |> addOne |> addOne
It's going to call
addOne
every time, and same for F#'s iterator methods.
The pipe operator in F# exactly equals the calling syntax (a |> b
is exactly the same as b (a)
, just more convenient sometimes). And same in Rust, a.b()
equals b(a)
(or b(&a)
, or b(*a)
, or b(&mut *a)
, but that is because of deref coercion). The difference between Rust and F# in this example is that Rust iterators are lazily evaluated, and F# List
methods are not. Still, as mentioned bt @isaacabraham, F# has Seq
s which are lazy.
I'm not sure (warning: I'm not a Rust dev so might be completely wrong here) that the two examples (map.filter vs addOne |> addOne) are really equivalent.
Isn't the map. filter etc. nature of that sample more about library functions, iterators and how collections are evaluated? In F#, probably the closest would be using Seq e.g.
And indeed in the example above, no real computation happens until you "evaluate" the query using e.g.
Seq.toList
or some other function that executes the sequence.So, yes,
|>
does eagerly evaluate, it's really just syntactic sugar for calling the last argument in a function on the LHS of the pipeline - but that doesn't preclude the kind of lazy evaluation you're referring to.Might be completely wrong here though, so feel free to ignore this if I've misunderstood!