Hacker News new | past | comments | ask | show | jobs | submit login

The question is what "2.days" actually returns. IIRC, in ActiveSupport it returns an integer number of seconds. Is that a sensible convention? If you had another class representing an span of time (with or without any absolute calendar reference) that an Integer could convert into, that might be a cleaner design. But even in that case you wouldn't necessarily have a method on Integer to confer it to a TimeSpan; you'd initialize a TimeSpan with an integer argument.

In general I find it cleaner when less primitive classes can represent themselves as more or equally primitive classes, but not vice-versa. to_integer, to_float, to_list, to_boolean, etc. But on the other extreme, if your application entails establishing database connections, implementing String#to_database_connection would be ridiculous.

For me, integers are much more primitive than date and time objects. `2.days` would be equivalent to implementing `2.meters` in a CAD application, but I suppose you might be OK with that.

But even granting that an integer can transform itself into a representation of a time span, i.e. `2.days` is okay, `2.days.ago` is right out. That's not just an Integer representing itself as a span of time. Now all of a sudden Integer has to have knowledge and, even worse, opinions about the current date and time (which is one of the most hazardous boundary cases in all of software).




> integers are much more primitive than date and time objects

In terms of concepts, yes, but in terms of language syntax and semantics they are the same (in Smalltalk).

> `2.days` is okay, `2.days.ago` is right out

Yeah. But not because it "looks bad", and not because Integer would need to know anything more than it does already (it wouldn't), but because `2 days ago` is actually less flexible and harder to maintain.

Consider:

    Date today - 2 days. "27 December 2013"
    DateAndTime now - 2 days. "2013-12-27T04:43:42.679+01:00"
We can get either Date or DateAndTime without changing anything. With proper planning the superclass of both Date and DateAndTime can have a generic - method which will work for all the cases. With the `2.days.ago` we're tied to one result type. We need to code around it, giving it a default argument with a class or remembering the "kind" of Duration.

Anyway, if it's ok to have a function which takes integer it's equally ok to have an Integer have a method. Especially when you just can't have a function, like in Smalltalk and the only alternative is writing yet another class with one method in it.


> Anyway, if it's ok to have a function which takes integer it's equally ok to have an Integer have a method.

Seriously? Any function that takes an integer argument? Like, "exit". You want to call 1.exit to terminate a program with a return code of 1? Is there any difference between a function that takes an argument and a method on an object? Shall we implement all functions that take n arguments as multimethods? Are you actually going to implement String#to_database_connection? That was meant as a reductio ad absurdum, I didn't expect anyone to literally follow me there.

If you want some particular Integer to get decorated with additional methods because you're using it in some particular domain context but you're not globally changing the interface of all Integers that's a different story.

> Especially when you just can't have a function, like in Smalltalk and the only alternative is writing yet another class with one method in it.

There are perfectly same object oriented ways to solve this problem, like instantiating some kind of TimeSpan object, or having a singleton for process status in the case of the "exit" function.


> You want to call 1.exit to terminate a program with a return code of 1?

1.exit is a bad idea because it has a side effect outside of it's receiver, I think.

> Are you actually going to implement String#to_database_connection?

Yeah. Or at the very least I won't die when I see this (working code in Pharo 2.0):

    'http://example.com' asZnUrl retrieveContents. "connect to given url and fetch it's contents"
or this:

    'HelloWorld' asMorph openInHand. "create a bitmap representation of string and display it on screen where the cursor is"
or this:

    #asSet value: (Array with: 1 with: 1 with: 3). "Symbol>>value:anObject looks like this: anObject perform: self"
which is especially interesting: how come a Symbol knows how to perform an action it can represent on some given object? I dunno. It just does.

I can't tell you exactly when adding a method to some class begins to be a bad idea and when it's still ok to do. I don't know any hard rule for this. I agree that `1.exit` is a bad idea and I provided a rationale for why I think so. But I'm still convinced that there are many (many more than you seem to think) cases where it's ok, it's acceptable and convenient to extend String, or Object, or any class you don't "own".

I'm actually looking at the system which is built with very many methods like the examples above (although it doesn't have 1.exit) and I see that it works. One of the most basic classes, Object, has this many external (with extension methods) protocols in it:

    *Collections-Abstract-splitjoin, *Fuel, *Fuel-Collections, *Glamour-Helpers, *Graph-ET-Core, *Graphics-Display Objects, *Kernel-Exceptions-debugging, *Monticello-Storing, *Morphic, *NativeBoost-Examples, *NativeBoost-core, *Nautilus, *Polymorph-EventEnhancements, *Polymorph-TaskbarIcons, *Polymorph-Widgets, *Ring-Core-Kernel, *Shout-Parsing, *Soup-core-testing, *Spec-Core, *Spec-Tools, *System-Settings-Browser, *System-Support, *Tools-Base, *Tools-Browser, *Tools-Explorer, *Tools-Finder, *Tools-Inspector, *UIManager, *VMMaker-translation support, *collectionextensions, *deprecated20, *eyesee-support, *grease-core, *grease-pharo20-core, *magritte-model-accessing, *magritte-model-actions, *magritte-model-model, *magritte-model-testing, *magritte-morph-converting, *metacello-core, *mondrian-complexshape, *mondrian-core-accessing, *mondrian-fadelayout, *necompletion-extensions, *neo-json-core, *petitparser-core-converting, *petitparser-core-testing, *roassal-core, *rubric, *tools-debugger
And the system still works. And it's easy to navigate. And to understand. And to use, even. So, once again - I'm not arguing that 1.exit is ok, just that there are many "saner" extension methods which are ok. To get the above list I had to write some code:

    |allProtocolNames externalProtocols|
    allProtocolNames := (Object allMethods collect: [ :x | x category asString]) asSet asSortedCollection. 
    externalProtocols := (allProtocolNames select: [ :x | x beginsWith: '*' ]) 
        inject: Character cr asString 
        into: [:acc :x | 
            acc , x , Character cr asString
        ].
    Transcript show: externalProtocols.
I may not be very experienced Smalltalker, but I believe this is more or less idiomatic Smalltalk code. Even in this short snippet there are 3 asSomething methods - and the code still works and I still think it's readable. And cute.

Is it Smalltalk specifically which enables this technique or what other rules extension methods need to follow to be sane I don't know. Which is why I'm hesitant to implement those methods myself. But I accept their existence and the fact that they have their place in a sound, sane design, at least if done right (for any acceptable value of "right" we can agree on).

But well, my day to day job is writing Python, where "monkey patching" is considered a sin and the community consensus is pretty much the same as your opinion: don't extend classes, write functions instead. So I kind of understand this position. Although I still think it's not inherently bad idea to extend base classes and that it's the matter of language design, project design and tooling. But I can't say for sure. I'm just telling you what I saw when I went and learned Smalltalk, that's all.


This is surprising enough I'm not sure how to respond anymore.

> which is especially interesting: how come a Symbol knows how to perform an action it can represent on some given object? I dunno. It just does.

Presumably it just sends itself to the object you pass in? This is not something I'm actually bothered by.

I guess it depends on how you extend classes you don't own. If the adapters themselves are modular and are only loaded with the class that they're trying to adapt to, that might be OK. In other words, if I have a DatabaseConnection class that extends other classes with asDatabaseConnection methods, and that extension only happens when I load the DatabaseConnection class, that still satisfies the intention of SRP even though I'm technically implementing methods on other classes.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: