Hacker News new | ask | show | jobs
by Animats 3629 days ago
The idea that each type has its own control flow primitives is bothersome. It's taken over Rust:

    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        .map(|n| 2 * n)
I'm waiting for

    date.if_weekday(|arg| ...)
Reading this kind of thing is hard. All those subexpressions are nameless, and usually comment-less. This isn't pure functional programming, either; those expressions can have side effects.
8 comments

I don't agree here at all. The methods you show operate on an Optional, and it's incredibly common to perform those kinds of comparisons so it makes sense that they have convenience methods. This is not at all comparable to something like if_weekday.

This has not "taken over" rust. Result is another type that does this, but this makes sense for the same reasons.

Rust basically offers a Monad-like API there. That's perfectly fine and a well established pattern.

That has nothing to do with primitive control flow nor is that an indication of if_weekday appearing anytime soon.

That being said having primitive control flow implemented as methods also has precedent with languages like Smalltalk or Self. That may be unusual but I don't think that's necessarily bad. I would be interested in reading about why this is bad design though.

In Haskell, realizing that data flow and control flow are of the same spirit and that data structures are control structures is one of the key epiphanies to be had.

This article mentions 'if' and 'Boolean'. Loops and lists are another example. (And for the same reason that most languages make such extensive use of loops, Haskell programs can often have a lot of lists.)

The annoying part there is the repeated "|x| x.". Rust should have syntax to reference a method of an object, instead of having to write a wrapper. So it'd look like .map_err(???.to_string()).
Groovy and Kotlin use the implicit "it" parameter for lambdas that take just one parameter, which is very convenient:

    listOf(1, 2, 3, 4).filter { it % 2 == 0 }
Scala allows underscores, and sequential underscores refer to the next element, so you can do e.g.

    list(1, 2, 3, 4).reduce(_ + _) == 10
Doesn't that make the parameter anonymous, though? Can you println that _ and see the value of the current element?
Use a function that prints then returns its parameter:

  list(1, 2, 3, 4).reduce{print(_) + _} == 10
Use it if you or your language hasn't defined such a function:

  list(1, 2, 3, 4).reduce{it:= _; println(it); it + _} == 10
In fact, any name for it will do.
Yeah, that's the tradeoff–gain the ability to work with multiple parameters but lose the ability to reuse a single one.
And Haskell uses operator sections: (==0) would be similar to { it == 0 }. Alas, the section syntax get a bit cumbersome when you want to compose two or more of them, like in this translation of your example:

    filter ((0==) . (%2)) [1, 2, 3, 4]
so does LiveScript
It does. This could have been written

    .map_err(ToString::to_string)
as well. Works just fine with methods.
Why can't it be written as:

    .map_err(to_string)
When using the lambda the type is inferred, so why should there be a need for the ToString?
It'd need syntax to distinguish from a local function called to_string. Anything less allows ambiguity and wouldn't be equivalent (like using type::method won't do auto-borrow). So it'd need to be "\to_string", "|to_string" or something. Ample opportunity for bikeshedding.

Functional style is hampered by excessive verbosity. (Non functional style is so verbose a bit of extra noise doesn't hurt _as much_.) Rust could use a lot more inference, custom operators[1], and so on. They seem to sort of agree, with auto-deref, auto-borrow, some type inference, but won't go all the way. I suppose being conservative can be defended -- can't go back without breaking code. Hopefully, in the future, the verbosity will annoy more people and there will be enough support to head in a more Haskell/ML direction. But they seem very opposed to it at the moment.

1: The rationale apparently being "someone might abuse it!" instead of "it makes good libraries even better". Parser combinators, UI toolkit code do great with custom operators. Require a method name (i.e. operator !!= as foo) if it's too great a concern. Can't save yourself from bad writers. Crippling yourself to avoid this seems like a poor tradeoff.

If `to_string` was a function that was imported into the local namespace, you could. But since it's a method, you can't; you need to provide the trait name.
If there's a function and a method named to_string the user would've to be explicit about which one he uses by adding a namespace, like ToString.

If there's only one to_string function or method, the compiler could just take this one.

To follow up on this slightly, it's not really that this is special syntax. It's that map_err takes a function as an argument, and this is how you refer to this method by name.
But that's actually longer. It does look a bit better though, maybe.
Yeah, it's not about saving characters to me, it's about clarity.
Actually, this suggestion does not even work. Writing trait::method is not equivalent to writing "|x|x.method()". The latter will use method lookup rules, the former requires the programmer to decide which impl. For instance, in the above example, if the type impl'd to_string, that would be the one used, not ToString's implementation. From what I can tell anyways: https://is.gd/E8pdWc

Edit: Also, auto-borrow does not seem to work with this syntax.

This is a common enough pattern that reducing the visual noise will increase clarity.

Yes, this is correct. It's what I was getting at in the other thread; this is choosing a method manually.
Rust does have such a syntax: map_err(ToString::to_string).
It's not the same though; it can resolve to different methods based on non-local code. It also doesn't do autoborrow.
The method whose to_string method you want to reference isn't in scope. You need a function that calls the method on the argument it's called with.

Why add a feature for this - worse syntax, if you can just use an anonymous function?

Rust is already not the simplest of languages, adding further syntax and features of questionable benefit won't make the language any simpler or easier to understand.

Because repeated "|x| x" is a common noise pattern.
Most of this should be obviated when the ? operator is ready. But until then, there is no primitive for 'work on the type you wrapped in Option, short-circuiting and returning None at the first sign of failure', so it has to be done in the library.
If this is an accepted idiom, people will be using it for years to come. Sometimes only to be cool.

With "?" and "try!()", Rust is sort of emulating exceptions in a weird way.

I started using a similar idiom in Java 8 recently, inspired by Optional class and some new functions in Map interface. It took me an hour or three, but at some point I noticed that in few places I was writing like this just because it's "cool", even though it was less readable (and actually made it harder to use one code-navigation IDE extension I liked).

A lesson this strengthens in me again is that sometimes a cool-looking idea turns out to be pretty bad in practice, but you only figure it out after you go ahead with it. It isn't bad to try out things (that's how we learn), but you need to be extra honest with yourself about how the thing really feels when you're first using it, and never ignore that sense this new idea actually doesn't fit well and should be rejected.

  >  Rust is sort of emulating exceptions in a weird way.
Except at the level Rust operates at, the code generation for exceptions vs. not is a thing. Not relying on landing pads, etc, is a thing people care about, and is why there's significant difference between return values vs exceptions can matter, even if at some higher semantic level, they're roughly equivalent.
I like it, because you get somehow the best of both worlds, on one side you're getting explicit error handling and on the other the convenience of exceptions, that you can just "raise" them and they propagate upwards in the calling stack.
I'd prefer to use `if let` and/or `try!()` instead of the methods here.
Oh god it's like ActionSupport came back with a vengeance.
Rust does not let you globally add things to existing stuff, so it's a very different situation than ActiveSupport, even if it may look superficially so.
date.if_weekday looks like something one would find in Smalltalk class library, but (at least for GNU Smalltalk) only such construct I've found is this: http://www.gnu.org/software/smalltalk/manual-base/html_node/...