9 ms·
Sometimes I think I must be a humongous idiot because adding a "days" method to Fixnum smells like the stupidest, most counterproductive, cutesy bullshit to me,
by hamburglar 13y ago
Sometimes I think I must be a humongous idiot because adding a "days" method to Fixnum smells like the stupidest, most counterproductive, cutesy bullshit to me, but some pretty smart folks think it's great (that's a compliment btw).
Stuff like this is the opposite of expressive to me because cute expressions that read like english scream "please take a moment to analyze this and make sure it does what it sounds like." I am instantly suspicious of code that is so expressive that you can just intuit out its purpose without thinking it through. Code is too subtle for treating it that way.
- jhspaybar 13y agoThis comment makes no sense to me, you don't like how clear it is? Somehow the clarity causes you to distrust what is actually happening? Every time I have to write something like Calendar cal = Calendar.getCalendar(); cal.setDate(new Date()); cal.addDate(-2, Calendar.DAYS); Date date = cal.getDate(); I want to claw my eyes out. Is this what you consider more clear, less cutesy, somehow more productive than just 2.days.ago ?
- wheaties 13y agoHow about this 2.days.from_last_update? What does it do? What is going on with "from_last_update"? Is it actually reaching out to a database to find the last updated value? Which database? How do you configure it? All these questions and more are why you should question "english as a DSL" methods. Granted, there's a ton of great libraries out there which do this well. Take Unfiltered's handling of parsing out requests for pattern matching: case POST(Path(Seg("api" :: "calls" :: name :: Nil))) => this is "englishy" without having you scratch your head. I've also seen case POST(Path("/api/auth")) & Authenticate(user) => which then causes a DB access to happen in a configured implicit value. Now try to reason out why something is not hitting a URL or being denied when you first get into a project. Unless there's someone to explain it to you, it just happens like "magic." TL;DR How much magic do you want to deal with?
- bad_user 13y agoYour example is a sort of strawman argument, probably constructed without you being aware of it. "2.days.from_now" is indeed not free of side-effects, however "now" is a function that depends on the computer's internal clock and is generally understood what it does, the clock being a sort of global state that mutates and is generally available, whereas "2.days.from_last_update" implies usage of local mutable state, passed implicitly and highly dependent on the context you're in. Also, this example is simply wrong ... case POST(Path("/api/auth")) & Authenticate(user) => It's wrong, because this is a route matcher, so if the user is not authenticated, the server would throw an HTTP 404 Not Found status. HTTP 404 Not Found is not meant to be thrown in case the user is not authenticated. There are more relevant, more correct status codes for that, like 401 Unauthorized or 403 Forbidden, or heck, in case of classic websites, a 302 redirect to a login page is in order. So you're basically giving broken examples and calling them "magic", but the examples are obviously broken, the first one in terms of API design, the second one in terms of functionality. I really do understand your hint. But the examples you're giving are bad because they rely on totally non-obvious mutable state that's passed implicitly. This is indeed "magic", however it has nothing to do with syntactic sugar. Btw, in Scala 2.10, thanks to String interpolation, you can also construct route-matchers such as this on [1]: case POST(p"/api/calls/$name/") => Doesn't work by default, you really have to build your own stuff for this to work (see the linked example). Or if you want to throw in regular expressions, you can either do this: case POST(p"/api/calls/${Integer(id)}/") => Or you can have like a mini-DSL with regular expressions built-in: case POST(p"/api/calls/$id<\d+>/?") => // note the optional ending slash Yeah, you can do this. And it's certainly much nicer to me, more flexible and you can also make it faster than pattern-matching on Lists, as in your Unfiltered example. Is this magic? I don't see a local, mutable and implicitly-passed context, so for me it doesn't qualify. Also, whenever I see "2.days" or "4.kilobytes" in Scala code, it's certainly nicer than seeing "172800 /* 2 days in secs /" or "4096 / 4 kb */". Given that these are statically type-checked extension functions that do not modify the original Int type and that do not rely on some mutable local state passed implicitly, is this magic? My IDE doesn't think so. [1] https://github.com/alexandru/shifter/blob/master/web-api/src/test/scala/shifter/web/api/mvc/PathMatcherSuite.scala https://github.com/alexandru/shifter/blob/master/web-api/src...
- ubernostrum 13y agoYears ago I got into an argument about these sorts of "beautiful" and "intuitive" APIs: http://www.reddit.com/r/programming/comments/21pnl/just_say_no_to_ruby_on_rails/c21rwo http://www.reddit.com/r/programming/comments/21pnl/just_say_... I still do not have good answers to the questions I asked in that first comment.
- mpartel 13y agoOne possible answer: static typing + autocompletion. Autocompletion clearly show what you can and can't chain, but admittedly it helps a little less when the DSL asks for a parameter of some type and you don't know how to start building one. Perhaps one way to evaluate the learning curve of an API is to count how many non-standard types appear in the signatures of its methods, excluding the `this` parameter.
- tizoc 13y agoFrom where is that? This is how it works in C# (and .NET in general) DateTime.Now.AddDays(-2) Not wanting to overload integers with calendar functionality doesn't mean you have to make it so cumbersome. Edit: typo
- usea 13y agoYou can also do: DateTime.Now - TimeSpan.FromDays(2)
- MichaelSalib 13y agoI feel the same way that hamburgler does. For me at least, you java calendar code looks horrible. I'm thinking something more like date.today() - timedelta(days=2)
- matthewmacleod 13y agoNote that this syntax is perfectly achievable in Ruby (w/ActiveSupport) too. The following expressions are equivalent: Time.now - 2.days Time.now.advance(days: -2)
- MichaelSalib 13y agoIf you can get exactly the same benefit, then why pay the cost of overriding behavior of the integer type and dealing with hidden black magic that makes your program harder to reason about?
- steveklabnik 13y agoI'm not sure which example you're talking about, but there's no additional cost in the Rust example. And it's not hidden; it's in the trait definition, which is scoped.
- philwelch 13y agoYou're conflating different things here. `Date.today - DateTime.days(2)` would be just as clear as `2.days.ago` without muddling the semantics of integers.
- steveklabnik 13y agoAs I mentioned in the post, > Now, I should say that I almost never use 2.days, but the point is that it’s a benchmark of expressiveness: if you can write it, you can do all sorts of other flexible things. I tend to use MiniTest, not RSpec. But I'm very glad RSpec can be built.
- danneu 13y agoI use https://github.com/sconover/wrong https://github.com/sconover/wrong with MiniTest which might qualify as another example of Ruby "tricks" to achieve a different interpretation of expressiveness. The result is the ability to express assert { :apple == :orange } assert { [:apple, :banana, :pear].include? :orange } and let the computer figure out the appropriate error message and expected-vs-actual diff.
- kybernetikos 13y agoI 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.
- username223 13y agoThe folks who "'think' this is great" are the same folks who self-submit their blog entries to news.ycombinator.com: self-promoting quacks. Bournegol has been around for awhile: http://oldhome.schmorp.de/marc/bournegol.html http://oldhome.schmorp.de/marc/bournegol.html
- lmm 13y agoI found this a lot less problematic in scala with IDE support. You can write 2.days, with appropriate imports, but the IDE will highlight the implicit conversion on the 2 (a green underline seems to be the standard visual representation), and you can click through to the definition.
- lmkg 13y agoI think it basically comes down to trust. Can you trust that the person who wrote the library isn't a dingbat, and that they've thought out the edge cases and composability of the interface they've designed, and that they've taken care that code actually does what it looks like it does, and confusing cases are either inexpressible or at least look confusing so you know you have to look closer. In short, can you trust the author to design interfaces well. Personally I don't think it's inherently good or inherently bad, it's just one of those things that makes good code better (concise & expressive) and bad code worse (inscrutable & brittle). Glass-half-full and glass-half-empty are both valid opinions on the matter, and I strongly suspect which way you leans towards depends heavily on the quality of other people's architecture that you have to deal with on a daily basis.
- brandonbloom 13y ago> Can you trust that the person who wrote the library isn't a dingbat Almost never.
- philwelch 13y ago"Let's just throw away the single responsibility principle and give Integer a method that multiplies it by the number of seconds in a day, and another one that adds it to the current day to get a new Time or Date object. Obviously that's something that an integer should be concerned with."
- matthewmacleod 13y agoThat's kind of the philosophy of Ruby though, and ActiveSupport by extension. Where it makes code clearer and more concise, or where it simplifies an often-used idiom, and where it outweighs the benefit of sticking to principles—as I believe it does here—then breaking those principles is acceptable, in languages where coding conventions support this.
- philwelch 13y agoI don't think `2.days.ago` makes code any clearer or appreciably more concise than, for instance, `Date.today - Date.days(2)` (or even, to meet you in the middle, `Date.today - 2.days`).
- steveklabnik 13y agohttps://news.ycombinator.com/item?id=6976450 https://news.ycombinator.com/item?id=6976450
- jshen 13y agoWe must not mistake principles and guidelines for absolute rules. I'm not judging this particular case, but I see the SOLID guidelines treated as absolutes far too often.