Enumerate very slow?

Personally would try extracting the break; by finding (position) first. Not sure it would help with performance. Maybe on top consider a reverse find, (No idea without study, if expectation is index is towards end.)

So I tried following on from cuviper's example of moving variables out of the Tatami struct and making them parameters of the methods. Works fine but as I did that, one by one, the run time was slowing down noticeably. About two seconds each.

I backed them out again.

I think now I'm in the zone where tweaking this and that in the code will be changing the run time a bit with no obvious reason why. Perhaps just because the instructions moved around the cache some how. Futile attempts at micro-optimization.

The main point was to get that Rustic style and keep clippy quite. As such I think those program is cooked and it's time to move on.

Thanks for the advice here everybody.

1 Like

I would look on the docs for the type in question to find out what a method does. Now, array doesn't actually have many methods, apart from a few for trait implementations. But its top-level docs mention that arrays coerce to slices, and there you can find fn iter(&self) -> Iter<T>.

1 Like

It's very interesting to me how different people can be. That while let syntax was one of the trickiest "phrases" in Rust for me to pick up, whereas the chained iterator methods pretty quickly just read like prose.

4 Likes

I think that is the central theme of this thread: different people from different backgrounds find different ways of expressing intent in code more intuitive. Which is, BTW, one of the reasons I love Rust. It doesn't pick a side. It allows both imperative loops and mutability (but safely without a GC) and functional immutability. Which is awesome.

1 Like

It's very interesting to find that I have been living, working and programming on a different planet for four decades!

I first programmed anything in 1975, in BASIC and assembler. Than came Algol. Then came the world of work with a dozen languages, PL/M, Coral, Ada, C, C++, Javascript(node.js) etc. I have worked on all kind of projects in various domains, from embedded military systems to CAD packages to simulation.

Not once in all that time did I see any code anywhere that used anything like the 'functional' style that is recommended with Rust. No .iter, .map, .filter, .zip, .this, .that. No lambda functions or closures. Nope it was all good old sequence, selection and iteration done with loops, indices and arrays. With various degrees of code organization as we get modules in one language, classes and objects in another etc.

In fact I had never hear of a lamba function or closure until I took on some Javascript(node.js) projects just a few years ago. Or if I had it was something they did in CS research projects not useful in the real world.

Except Javascript of course. A wonderful and sophisticated language where lambas and closures are a natural and pretty much essential due to it's completely event driven programming model.

I'm quite prepared to take all this on. But not at the cost of code readability, expressiveness, clarity. Not at the cost of performance. Certainly not both at the same time as is often the case.

So I wonder, where did all those people come from that find the verbose and obtuse 'functional' style more intuitive?

I have to strongly disagree with the generalization that it's always "verbose" and "obtuse".

In fact, I don't really agree with the sentiment that different people prefer different styles: to me, it's not one or the other, but it depends on the situation.

I'm happy that Rust supports both, but not for an abstract reason of "diversity". The reason is very selfishly concrete: it is convenient for me. Both the imperative, loop-based, and the functional, iterator-based approaches can become annoying if overused, so it's good to have a choice.

What I observed on my own coding style is that when there is a simple and clear transformation or pipeline of transformations that I need to perform on a collection, I prefer iterator adaptors. Especially combined with type inference:

fn do_thing(strs: &[&str]) -> Vec<Cow<str>> {
    strs.iter()
        .map(|s| &s[3..])
        .filter(str::is_empty)
        .skip(8)
        .map(Cow::Borrowed)
        .collect()
}

is much clearer (and a lot easier to get right) than

fn do_thing(strs: &[&str]) -> Vec<Cow<str>> {
    let mut i = 0;
    let mut v = Vec::with_capacity(strs.len());

    for s in strs {
        let s = &s[3..];
        if s.is_empty() {
            continue;
        }
        i += 1;
        if i > 8 {
            v.push(Cow::Borrowed(s));
        }
    }
}

(Edit: see, I got the >= vs > wrong in the imperative variant the first time around!)

On the other hand, ask me to perform a breadth-first search or an algorithm that makes heavy use of "irregular" control flow, or just control flow in general that is hard to express using the linear nature of iterators, and I immediately fall back to the imperative style, because that's how the code becomes clearer to me.

9 Likes

Sorry, yes, I do have a habit to over generalize.

My "verbose" observation comes from the suggestions of clippy when it does not like my array indexing and those of others here when I ask about it.

When I say "obtuse" I mean that imperative style has very little conceptual baggage overhead. All there is is 'loop', 'if', 'array', 'index'. Understand that and you can tell what is going on. Meanwhile the function version requires the reader to know, 'iter', 'map', 'filter', 'skip', 'collect' and a whole zoo of others. Not only that they need to know about lamda functions and closures.

See stevensonmt's suggestion for my problem above as an example of obtuse.

Quite likely that is only obtuse to me because I have never seen that in any code base I have worked on in a life time. With any luck I will become familiar enough over time with the most common cases and that it will no longer stall my mind and bug me.

Hence my question about where everyone has learned and become familiar with the functional style? I have not seen it in the work place. I have no seen it in CS course videos on YouTube. I don't know anyone personally who is familiar with it.

I do like your attitude. Functional style is not compulsory. No not obsess over using it. Use what is best for the job at hand.

2 Likes

If you're referring to this code I'd just point out two things:

  1. I wasn't trying to write production code, just answering a question about how to perform the equivalent of a for loop break using iterator methods;
  2. I'm not very good at this and should not be used as an example for or against any programming style.
1 Like

I have used the functional style heavily for things like ad hoc statistics gathering on a large number of log files.

Conversely, map is great when I want to generate a templated sequence. I’ve use functional code to generate lookup table files for imperative code.

I notice your list of languages didn't include any functional languages. I learned this style when I started using Haskell almost two decades ago. As you say, JavaScript also supports this functional style, and so does Python, to cover two widely known programming languages.

As for its obtuseness, I'd say that any unfamiliar idiom is obtuse, but that doesn't make it less clear. You could similarly argue that for loops are obtuse, when compared with while loops (especially in C/C++), since while loops have less syntax to learn and are just as powerful. But the whole loop is harder to read, since it scatters the initialization, update, and check throughout the code, so they may not even all be visible on the screen at the same time. Similarly, the functional style has more names to learn, but has the advantage that you don't have to reason through the logic of the program in order to discern what some coffee is doing. An example:

  let n = v.iter().filter(|x| *x != 0).count();

vs.

  let mut n = 0;
  for x in v.iter() {
    if *x != 0 {
      n += 1;
    }
  }

Neither code is particularly unclear, but the functional style doesn't require me to read through two separate lines in order to recognize that counting is happening.

6 Likes

I was. But please don't think I was picking on you in particular. Yours was just an example of a style that I am not familiar with and don't understand the reasoning for.

That is true.

It's true because in four decades of programming I have never seen a project using any 'functional language". Not in embedded systems, not in industrial process control, not in avionics systems, not in secure military communication systems, not in CAD or simulation systems. Nowhere.

"Functional programming" and "functional languages" like Haskell have only shown up on the radar in very recent years.

Firstly because Javascript is in someway inspired by such things.

This year because I find Rust. Which holds out the hope of being an actually practically useful safe systems programming language. Which it does in large part by adopting functional programming ideas.

Rust is the meeting of two worlds. You have lived in one. I have lived in the other.

It might take me time to adjust...

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.