
Python Idioms in Rust - cicero
http://benjamincongdon.me/blog/2018/03/23/Python-Idioms-in-Rust/
======
marshray
Very strange to see the core primitives of functional programming being sold
as 'Pythonic'.

I suppose we all look at the world from the perspective of our own experience.

~~~
apendleton
Honestly I suspect that this stems from a perception particularly among people
from a non-traditional CS background that "functional programming" is
intimidating, whereas Python is seen as approachable in a get-shit-done kind
of way for programming novices. Python definitely did co-opt a lot of
traditionally functional stuff, but did it in a way that obviates needing to
know what a monad is, concern oneself with functional purity, etc., so there's
a community of people now who don't care about any of that but are still
familiar with some of these idioms, and this post is meant to make Rust look
more accessible to them in a way that "Rust supports functional programming
idioms" would not.

~~~
coldtea
> _Honestly I suspect that this stems from a perception particularly among
> people from a non-traditional CS background that "functional programming" is
> intimidating, whereas Python is seen as approachable in a get-shit-done kind
> of way for programming novices._

Just people from a "non-traditional CS background"? Most CS graduates today
will say the same thing with regards to e.g. Python vs Scheme or CL.

> _Python definitely did co-opt a lot of traditionally functional stuff, but
> did it in a way that obviates needing to know what a monad is, concern
> oneself with functional purity, etc._

If you hanged around HN pre 2010, you'd seen that FP was all about Lisp (and
things like map, reduce, macros, first class functions, etc), and few gave a
rat's arse about "monads" and "purity" \-- it was only starting to emerge in
general consciousness, not dominant as it is today in FP circles.

~~~
pjmlp
As someone that learned FP when Haskell did not exist and Miranda papers were
still fresh, this mentality that only Haskell matters as FP notion really
feels odd.

~~~
OtterCoder
I personally feel like Haskell makes a good reference implementation for
functional programming. Whenever someone is explaining how to do some
interesting FP thing in, say, JavaScript, I usually just ignore them and go
read about how the same thing is done in Haskell, which is usually clearer and
less burdened with the complexity of getting a mutable, proceedural, eager
language to behave like an immutable, lazy, functional one.

~~~
jholman
If you think that immutability and laziness are inherently part of functional,
then you're part of the new-school-is-the-only-school problem.

I mean, I'm not saying that immutability and laziness aren't interesting. But
FP has a long long history, most of it's not lazy, and immutability is nowhere
near universal.

------
yxhuvud
> Personally, I like Rust’s extensive iterator functions even better than
> Python’s

Funny, when the rust example that gave rise to the comment looks more or less
like Ruby[ * ] but with other syntax for blocks. The writer should get around
more and try more other languages.

[ * ] Or any of a zillion other object oriented languages that have decent
support for functional looking code.

~~~
apendleton
My understanding, though, is that idiomatic Ruby inverts the control for
iteration (array.each(|thing| do stuff)), which leads to slightly different
ergonomics, whereas idiomatic Python is like what Rust is now; see
[http://journal.stuffwithstuff.com/2013/01/13/iteration-
insid...](http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-
out/) for a comparison. Interestingly, Rust used to have iteration styled
after Ruby's, and explicitly switched in rust 0.7:
[https://www.reddit.com/r/programming/comments/1hl2qr/rust_07...](https://www.reddit.com/r/programming/comments/1hl2qr/rust_07_released/)

I think the comparison here is also to Python because the specifics of the
API, rather than just the patterns, are pretty obviously inspired specifically
by Python (names of functions like zip, etc.), as opposed to other external-
iterator languages like C#

------
sampo
Not sure if there is any point in implementing map_on_vec using map, but this
code

    
    
        fn map_on_vec(vec: &Vec<i32>, func: fn(i32) -> i32) -> Vec<i32>
        {
            let mut new_vec = Vec::new();
            for i in 0..vec.len() {
                new_vec.push(func(vec[i]));
            }
            new_vec
        }
    

doesn't need to be based on mut, push and for. Something like this works, too:

    
    
        fn map_on_vec(vec: &Vec<i32>, func: fn(i32) -> i32) -> Vec<i32>
        {
            let new_vec: Vec<i32> = vec
                .iter()
                .map(|x| func(*x))
                .collect();
            new_vec
        }
    

At least it runs a tiny bit faster, without all the pushing.

~~~
shepmaster
And getting into idiomatic Rust + a bit of code golf + a bit of showing-off:

    
    
        fn map_on_vec(vec: &[i32], func: F)
        where
            F: Fn(i32) -> i32,
        {
            vec.iter().map(|x| func(*x)).collect()
        }
    

\- There's no need for the temporary variable, can just return directly

\- The `vec` argument should be a `&[T]` not a `&Vec<T>` [discouraged]

\- The `func` argument shouldn't be a function pointer but instead take an
unboxed, generic closure (no forced pointer indirection, can take a closure).
You may even want to accept a `FnMut` to allow the closure to mutate itself.

[discouraged]:
[https://stackoverflow.com/q/40006219/155423](https://stackoverflow.com/q/40006219/155423)

~~~
leshow
shep! you forgot map_on_vec's return type. Thanks for writing the comment
though, I was just about to point out Fn

------
recursive
> Neither Python nor Rust has ternary operators (which may be for the best).

Then it goes on to show the ternary conditional operator from python.

------
dom96
If as a Python programmer you're looking for a compiled language that is
similar to Python, check out Nim: [https://nim-lang.org/](https://nim-
lang.org/).

~~~
ubernostrum
Since it just came up again yesterday, it's worth noting that the term
"compiled language" is a tricky one to use here:

[https://nedbatchelder.com//blog/201803/is_python_interpreted...](https://nedbatchelder.com//blog/201803/is_python_interpreted_or_compiled_yes.html)

 _So: is Python compiled? Yes. Is Python interpreted? Yes. Sorry, the world is
complicated..._

~~~
leshow
I can't speak for OP but I think they are just differentiating between
languages that produce an executable binary versus one that is
interpreted/runs in a VM/is compiled to bytecode/whatever else

~~~
Can_Not
And Nim produces a small binary with the ability to tell the GC if/when/how
long to run, no need for any interpreter or VM. It targets c before final
compilation.

~~~
leshow
Okay? I wasn't talking about Nim or any language specifically.

------
raymondh
Thanks for the nicely written Python/Rust comparison :-)

Is Rust's flat_map() equivalent to this in Python?

    
    
        def flat_map(func, it):
            return map(func, chain.from_iterable(it))

~~~
edflsafoiewq
No, it's

    
    
        def flat_map(func, it):
            return chain.from_iterable(map(func, it))

------
monocasa
The 'single line if' is even cooler than that in rust, IMO. Everything being
an expression allows really neat combinations inside statements.

------
stochastic_monk
It’s worth noting that C++ has been adopting python’s functional attributes,
from range-based loops, <algorithm> , duck-typing, and fold expressions, to
libraries supporting Python’s style more directly. Range-v3 [0] and
cppitertools [1] stand out to me. The former is in a current proposal for
inclusion in the C++20 standard.

[0]
[https://github.com/ericniebler/range-v3](https://github.com/ericniebler/range-v3)

[1]
[https://github.com/ryanhaining/cppitertools](https://github.com/ryanhaining/cppitertools)

------
ubernostrum
I'm a bit surprised nobody has tried to start up the "what are tuples _really_
for in Python" argument in this thread.

The definition given -- "Python tuples act like immutable lists" \-- is
correct as a description of behavior, but the decision of when and whether to
use a tuple versus a list or some other structure can be a pretty contentious
one in some Python circles.

~~~
cup-of-tea
Lists are for iterating, tuples are for unpacking.

~~~
kzrdude
Wouldn't you say that lists are for homogenous collections and tuples for
short heterogeneous ones? Of course we use the Python "duck typing" definition
for homogenous.

~~~
cup-of-tea
You could, but I think mine is better. (firstname, lastname) and (x,y,z) are
homogeneous collections. But they probably should be tuples. In a tuple the
position describes the structure, in a list the position merely describes the
order. Hence why tuples are best if you plan to unpack them (ie. firstname,
lastname = name).

Trouble is Python doesn't completely separate the concepts like some languages
do (e.g. C array/struct, R array/list). You can unpack lists and store
heterogeneous items in them. Many libraries do this (e.g. SQL libraries will
give a list per row by default). And if you follow the bash read idiom in
Python using str.split you'll unpack a list as well. For me the distinction is
about documenting my code.

The only real practical advantage I'm aware of is tuples are hashable. I don't
think they are implemented anything like structs underneath.

~~~
lmm
> (firstname, lastname) and (x,y,z) are homogeneous collections.

Disagree. In a language with a decent type system you would give them
different types, because using a first name as a last name or an x as a z is
always an error.

~~~
cup-of-tea
In what language would you make firstname and lastname or x and y different
types?

~~~
lmm
Well, I would in Rust for starters.

~~~
cup-of-tea
Interesting. I should probably try one of these typed languages. When I was
thinking of types I was thinking about "physical" types like in C, not
abstract types.

~~~
dllthomas
Yeah, it's pretty common to think of types as "representational", and that
really doesn't get at most of what they are good for.

Even in C you can get some good help from the type checker if you know how to
use it. For instance, see [https://dlthomas.github.io/using-c-types-
talk/slides/slides....](https://dlthomas.github.io/using-c-types-
talk/slides/slides.html#26.0) for a technique that can turn a flakey segfault
from misuse of an API into a compile time error (later slides do just that for
a simple SDL program, with a modified header file).

------
weatherlight
looks more like ruby to me than python.

------
cup-of-tea
The Rust version of "zip" seems a bit odd as it's implemented like a method
rather than a function. Little things like make me not enjoy a language as
much.

~~~
coldtea
> _as it 's implemented like a method rather than a function_

Why should it be a function? It can be a method on sequence like objects
(trait)...

Python itself has tons of those...

~~~
cup-of-tea
Because the parameters to zip aren't positional, they are just multiple
parameters of the same type.

Python does have some that annoy me a bit. I think str.join would be clearer
as a function, but at least that does have a positional parameter.

~~~
coldtea
> _Because the parameters to zip aren 't positional, they are just multiple
> parameters of the same type._

How is that a counter-argument? It makes sense then that the method belongs to
that type (iteratables).

------
tabtab
Sorry, but language doesn't matter that much if the entire environment/stack
is decent. If you are using overly "clever" features of the language, then you
may be writing write-only code that has a high probability of tripping up new
hires.

I see too many trying to reinvent the database or operating system in
application code. Don't.

~~~
GolDDranks
What that has to do with anything? The author didn't advocate for reinventing
the database or operating system in application code.

Also, Rust is very much an antithesis of write-only code; it's specifically
aimed for programming at large.

