Python Idioms in Rust
benjamincongdon.me
benjamincongdon.me
I suppose we all look at the world from the perspective of our own experience.
Yes, but in addition to that, I think you can likely sell an idea to people much easier when it's couched in something they are already familiar with. There's been lots said about the functional programming capabilities of Rust, but if people don't already equate what they do as following some functional paradigm, they might find themselves uninterested in the discussion.
I think Python especially, with its "one way to do it" and (my perception) how people adhere to "the way to do it in Python", there's a good chance some people don't really know when they are using common functional patterns.
Despite being a core tenet of the language, Python is _really, really_ bad at following that rule. I agree with everything else you're saying, though. Those are still Python idioms even if they originated elsewhere.
'foo ' + bar
'foo {}'.format(bar)
'foo %s' % bar
f'foo {bar}' Template('$foo bar').substitute(foo='foo')> There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch.
If I remember my history correctly, this was poking fun as Perl's "TIMTOWTDI" [2].
It also says one obvious way to do it. It doesn't preclude there being more than one way.
[1] https://www.python.org/dev/peps/pep-0020/
[2] https://en.wikipedia.org/wiki/There%27s_more_than_one_way_to...
I didn't notice the date on the PEP, but I think you're right. I remember reading it when I learned Python circa 2002.
Found this (from 1999): https://mail.python.org/pipermail/python-list/1999-June/0019....
That said, I don’t see where the author put forward the idea that functional programming is Pythonic so much as “these are functional things that are great in python, here are some analogs in rust”.
In the same way, map and filter were often covered, but not always with an explanation of the style of programming they supported, or why that style could be useful. I do remember the original Dive Into Python did a good job of this, and of encouraging people to explore a more functional style, but I don't recall many other resources, at least from the (2004-ish) era when I was first learning Python, doing that.
As a result I have to be a bit careful these days; I still use Python's more functional constructs regularly, but I've learned to go easy on them when collaborating with others, since I know a lot of people who learn Python now are using materials that mostly only touch on list comprehensions.
I guess it's a "sheep as a lamb" thing -- "The 'functional way' is either points-free or pointless."
Maybe it's better in Rust because the method-style `x.filter().map().reduce()` lets you read left-to-right, and the "global function" `reduce(map(filter(x)))` has to be read inside-out. If there is a good reason to do Python's global function way, maybe D's UFCS can bring them the best of both worlds.
Which is annoying, because there are times when using map or filter is cleaner; for example, if you're doing pipeline-y stuff, using the function-based approach is much easier to read and reason about than doing a bunch of nested comprehensions.
I also tend to think people don't use itertools enough. But it catches people by surprise enough that several of my messing-with-interviewers canned solutions to common problems make heavy use of itertools (including a FizzBuzz which contains no 'for' or 'if' statements).
For example, I've recently found a way to use Python-like type hints in C++. Crazy, huh? The trick is to replace "auto" with the concrete type name, e.g.:
auto x = 5;
becomes:
int x = 5;
Secondly, that’s not a ‘type hint’ and c++ did not get this from python. Prior to c++17, there was no auto and all declarations were like this. If anything, Python got the names from c++
> did it in a way that obviates needing to know what a monad is, concern oneself with functional purity,
Caring about both of these things in the context of functional programming essentially dates from the early 1990s. According to Wikipedia (I have no better source, but it matches what I know of pre-1990s functional languages),
> Eugenio Moggi first described the general use of monads to structure programs in 1991.
Functional programming existed before then. Scheme, an early functional language, is from the 1970s. It has basically all the FP idioms that you have in Python, is not pure, is dynamically typed, and the doc never once references monads.
I'd actually say the definition of FP has shifted towards pure, category-theory oriented languages, but "traditionally functional stuff" definitely doesn't include monads.
Lisp is even older, as was touted as the de facto functional language since forever. And earlier MLs didn't have monads either.
And of course, just because Moggi described monads in 1991, it doesn't mean it didn't take over a decade for them to make any kind of significant dent.
(Assuming that they have made much of a dent today, which outside the world of Haskell, their explicit (as opposed to implicit) use is close to 0, anyway).
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.
My recollection was a bit different. I got interested in Haskell around 2007 or so. And pre-2010, I recall encountering several articles about that on HN, though obviously there were often articles about the Lisp family.
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.
What made Bill Nye awesome was not only his passions for the subject matter but a solid background that let him communicate complex topics in a simple and engaging way.
I mean, what's a scientist anyway? If it's someone in constant pursuit of knowledge, does a constant reading of new material to learn more count?
What about the person that actually does advance knowledge, for their self, but doesn't share it? Are you not a scientist if you don't let everyone know what you found? You would conceivably still be performing science.
Maybe the easiest way to define it as someone that intends to discover something new (or at least add new data to existing data, for corroboration or refutation)? I don't consider myself to be a scientist, but if I wanted to explore some topic in depth, and and started reading about it, and maybe performing my own small scale experiments to confirm what I had read, I think I would be "doing science" and I would then be at a minimum am amateur scientist.
Some particular names used in the examples were introduced by Python. For example the `enumerate` function or `zip`. I haven't seen those names used in programming languages older than Python, I know I might wrong. The point is that it's not crazy to believe that Rust design does borrow some things from Python (among other languages of course).
The article is comparing some particular Python idioms with Rust. The fact that those are functional I think is not particularly relevant. It's like someone seeing a comparison of Java and Go and saying that those are just the principles of imperative programming, or object oriented programming.
All the ML dialects that I know have a zip function and ML pre-dates Python by about 20 years. This is particularly relevant since Rust is clearly strongly influenced by ML.
> The fact that those are functional I think is not particularly relevant. It's like someone seeing a comparison of Java and Go and saying that those are just the principles of imperative programming, or object oriented programming.
The comparison feels right, but I think it would certainly be odd if somebody explained loops in Go as "a Java idiom".
All these names and ideas are clearly rooted in FP circles
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.
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#
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. 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
Then it goes on to show the ternary conditional operator from python.
https://nedbatchelder.com//blog/201803/is_python_interpreted...
So: is Python compiled? Yes. Is Python interpreted? Yes. Sorry, the world is complicated...
Is Rust's flat_map() equivalent to this in Python?
def flat_map(func, it):
return map(func, chain.from_iterable(it)) def flat_map(func, it):
return chain.from_iterable(map(func, it))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.
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.
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.
Another rule of thumb is that a lot of the time you can make code that uses tuples clearer by rewriting it to use namedtuple-defined struct values. If the language supported it, it'd probably also make sense most of the time to define a type for each tuple field, and have a type-checker look for errors.
Occasionally you see tuples used where they want immutable and hashable lists, but most of the time they're used as shorthand struct/"pod" tours types.
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.... 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).
(Again about Rust and C++ equally, if we'd only care about "physical" type, the string and the vector would be the same type. The distinct string type allows us to form the rules around exactly what kind of values we allow for or optimize for using that type.)
https://docs.rs/itertools/0.7.8/itertools/fn.zip.html
By the way, the Iterator's zip function is no magic. You could trivially implement a standalone function using the zip method yourself:
https://docs.rs/itertools/0.7.8/src/itertools/free.rs.html#7...
for (a, b) in Iterator::zip(as, bs) {
// ...
}Why should it be a function? It can be a method on sequence like objects (trait)...
Python itself has tons of those...
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.
How is that a counter-argument? It makes sense then that the method belongs to that type (iteratables).
I see too many trying to reinvent the database or operating system in application code. Don't.
Also, Rust is very much an antithesis of write-only code; it's specifically aimed for programming at large.