5 ms·
I think I agree with the thrust of what you are saying here. Just as English has rules with what you do with nouns and verbs etc, code has rules too that are d
by kybernetikos 13y ago
I think I agree with the thrust of what you are saying here. Just as English has rules with what you do with nouns and verbs etc, code has rules too that are different to English.
2.days().from_now()
says that we are performing the action 'days' on the object '2'. What could that possibly do? Well there're a bunch of options and none of them are obvious. It's weird and counter-intuitive. And then we go and perform the action 'from_now' on whatever the result of that was.
I don't mind 'fluent' style interfaces as long as they don't mess up the normal meanings of what objects and methods are, but sadly they usually do. Another classic sign of a bad interface is that with many of these fluent interfaces it's not obvious whether a particular thing should be a chained method call or argument, and if it's an argument, whether it should be got by calling a method, using a field or what.
Code should read like clear code. English should read like clear English. These are not the same things, and efforts to make them the same usually make a mess of both.
- xixixao 13y agoYou're saying `code` but what you really mean is "Java" or your favorite OOP paradigm. If someone comes up with different rules for what `2.days()` means then they are valid rules (and we can discuss whether they provide benefits to the other ones).
- kybernetikos 13y agoNot every nonsense 'rule' that someone comes up with is 'valid'. Rules need to be consistent. And yes, I was talking OOP since we were talking about ruby. Functional or logic programs also have their own rules that tell you how to understand them. If you want to create good programs you shouldn't mess with those basic rules in order to make the code look more like something it actually isn't.
- wycats 13y agoIn Rust, you have to import a trait (in this case active_support::Period) into the file that uses 2.days(). It's not a global change. If you see 2.days(), you know where to look for the definition, and the type system means that you can easily learn what 2.days() returns. In this case, 2.days() returns a `TimeChange` object (https://github.com/wycats/rust-activesupport/blob/master/dsl.rs#L7 https://github.com/wycats/rust-activesupport/blob/master/dsl...). `TimeChange` implements a `from_now` method here: https://github.com/wycats/rust-activesupport/blob/master/lib.rs#L30-L32 https://github.com/wycats/rust-activesupport/blob/master/lib... which returns a `Time`. This is a lot easier to reason about than global modifications to Fixnum in an untyped language.
- kybernetikos 13y agoYes, I took the fact that this was not a global modification of a widely used data type as a given, since to do so would be crazy in any language. And sure, you can always look up in the type system or (in a less well typed language) the documentation to see what it does. The point of writing clear code is that doing so should be a last resort and not something you force on developers in order to understand what's going on. Readability is what we're talking about here, and TimeChange.days(2) or new TimeChange(2, Days) are both more readable options than 2.days() because a programmer knows what is happening without querying information that isn't written in front of them.
- wycats 13y agoThis must be a question of aesthetics because the 2.days().from_now() seems perfectly obvious to me. Maybe someone can help me understand why it seems confusing?
- Touche 13y agoIt's non-obvious to me what 2.days() returns, that's the problem.
- matthewmacleod 13y agoI don't think that's a problem: avoiding features or fiddling with syntax in order to make the code more accessible to people who are not familiar with the language or its libraries isn't going to gain much in the long run. Languages and libraries should be geared towards long-term developer happiness, rather than a shallow learning curve. Anyway, I know as close as possible to nothing about Rust. It seems blindingly obvious to me that `2.days()` returns some kind of duration, and `2.days().from_now()` returns some kind of time. So I've looked at the code, and that appears to be correct - `2.days()` returns a `TimeChange`, and `2.days().from_now()` returns a Time. I can't think of any other reasonable interpretation.
- 13y ago
- matthewmacleod 13y agoCode should read like clear code. English should read like clear English. These are not the same things, and efforts to make them the same usually make a mess of both. This stinks of principle over pragmatism. Using the sugar offered by expressions like this requires a bit of knowledge of the development environment. I accept that it's a bit of a different syntax from some other languages, but that doesn't make it unclear at all. Speaking to Ruby (where ActiveSupport is from), we all know that it's a language that focuses on providing flexible syntax and "multiple ways of doing things", in contrast to some other languages which restrict flexibility to provide more consistently structured code. I don't think it's a bad thing at all to have these options available, and I often find my intentions are much clearer when writing, say: if post.created_at < 5.minutes.ago as opposed to Python: if(post.created_at < (datetime.now() - timedelta(minutes=5))) or Objective C: NSDate *now = [NSDate date]; NSDateComponents *delta = [[NSDateComponents alloc] init]; [delta setMinute:-5]; NSDate *fiveMinutesAgo = [[NSCalendar currentCalendar] dateByAddingComponents:delta toDate:now options:0]; if ([[post created_at] compare: fiveMinutesAgo] == NSOrderedAscending) That's where the magic comes in, and it makes Ruby ideal for things like web templating, while perhaps making it less suitable for other uses. TLDR: various constructs like this have their place, they're not inherently bad, but do require knowledge of the tools.
- robbles 13y agoI know this is a little nitpicky, but your Python example has way more brackets than are strictly necessary. if post.created_at < datetime.now() - timedelta(minutes=5): I agree with your argument in principle though. Just wanted to point out if you're trying to make a point about readability / ease of use with code examples, you should try as hard as possible to make each example optimized to avoid accidentally creating a straw man. Also, one criticism of the Ruby version is the invisibility of the types involved. What type is returned by 5.minutes vs 5.minutes.ago? How do I know I can compare the second to a date attribute from ActiveRecord? The Python version is more verbose, but I think it's more clear that I'm using two datetime's and a timedelta.
- jshen 13y agoIn both cases you have to either remember a lot of details, play with it in the reply, or look at the docs as you are writing it. Once it is written the ruby code is far easier to read.
- pcwalton 13y ago> 2.days().from_now() > > says that we are performing the action 'days' on the object '2'. What could that possibly do? It does what the language definition says it does: it tells the compiler to search through traits that were explicitly imported to find a method called "days" and calls that. (Since "int" is a built-in type, it has no methods except for those provided via traits that were imported, so traits are the only potential source of these methods.) If there is more than one matching method, Rust never guesses: it instead signals an error. > I don't mind 'fluent' style interfaces as long as they don't mess up the normal meanings of what objects and methods are, but sadly they usually do. In this case they don't. It falls out of what traits and methods already provide in Rust.
- Camillo 13y agoI call this phenomenon "language skeuomorphism". See this comment I posted some time ago: https://news.ycombinator.com/item?id=5056667 https://news.ycombinator.com/item?id=5056667