> There's nothing wrong with object methods (that's 100% pure syntax vs. a function call)
Not really; in OOP a method call like `foo.bar(baz)` sends the `foo` object the message `bar` with argument `baz` (in Kay's current terminology); or, in more 'mainstream' terminology, it looks for a method in the `bar` slot of `foo` and calls it with `baz`.
As far as I'm aware, this is a pretty core concept in OOP: if our code isn't organised along these lines, then we're writing non-OOP code which just-so-happens to use classes/prototypes/methods (in the same way that we can e.g. write OOP code in C which just-so-happens to use procedures/switch/etc.).
(Side caveat: I appreciate that I'm making some assumptions here; one of my pet peeves with OOP is that it's rife with No True Scotsman fallacies about what is "proper" OOP, e.g. see 90% of the content on https://softwareengineering.stackexchange.com )
There is a fundamental asymmetry between the roles of `foo` (object, receiver of message, container of methods) and `baz` (argument, included in message, passed into method).
A function call like `bar(foo, baz)` doesn't have this asymmetry: the order of the arguments is just a convenience, it has no effect on how the `bar` symbol is resolved (in most languages it's via lexical scope, just like every other variable). Swapping them is also trivial, e.g.:
In contrast, I can't think of an OOP alternative to this which isn't messy (e.g. adding a `rab` method to `baz` via monkey-patching).
Of course, the elephant in the room here is CLOS, but that's sufficiently different from most OOP as-practiced that it's better considered separately (e.g. I'd be more inclined to agree that CLOS methods are "100% pure syntax vs a function call")
> Non-syntactic aspects are maybe a more involved discussion. For an example, I personally think traditional OO lends itself very nicely to runtime polymorphism.
I also disagree with this, again due to the artificial asymmetries that OOP introduces (that objects "contain" methods). In particular, as far as I'm aware OOP simply cannot express return-type polymorphism. The asymmetry between arguments and return values is certainly more fundamental than the completely unneeded distinction between receiver and arguments, but it's still very useful to dispatch on the output rather than the input.
The classic example is `toString` being trivial in OOP (a method which takes some object as input and renders it to a string; the implementation dispatches on the given object); whilst `fromString` is hard (a method which takes a string and returns some object parsed from it; OOP can't dispatch the implementation by "looking inside" the object, since we don't have an object until we've finished parsing).
You can do that sort of thing in Kotlin, which is an OOP (well, multi-paradigm) language:
inline fun <reified T> String.fromString(): T {
return when {
T::class == String::class -> this
T::class == Int::class -> Integer.valueOf(this)
else -> TODO()
} as T
}
val str: String = "abc".decode()
val int: Int = "123".decode()
You have to be careful with type inference: if there's no way for the compiler to figure out what type you want from the call site, you'll get an error.
You might say this isn't OOP in the strictest possible sense, but extension methods + type inference + reified types gives what you're asking for in a way that's natural for an OOP programmer.
> You have to be careful with type inference: if there's no way for the compiler to figure out what type you want from the call site, you'll get an error.
I'd say that's a feature, not a bug :)
> You might say this isn't OOP in the strictest possible sense
I would say it's not OOP in any sense. Having one function implement all the different behaviours, and pick which one to run by switching on some data (in this case the class tag) is a classic example of being not OOP.
As a bonus, this ignores dynamic dispatch (the only implementation is for `String`) and it's not polymorphic (the same code doesn't work for different types; instead we have different clauses for each type).
This would be salvagable if `String::fromString` only enforced the type constraint, and dispatched to `T::fromString` to do the actual parsing, e.g.
inline fun <reified T> String.fromString(): T {
return when {
T::class == String::class -> this
else -> T::fromString(this)
} as T
}
I'm not sure whether that would work or not (I've never written Kotlin), but I still think it's "messy" (monkey-patching methods on to classes, reimplementing dynamic dispatch manually, inspecting classes (via the equality check), going out of our way to prevent infinite loops, etc.)
Not really; in OOP a method call like `foo.bar(baz)` sends the `foo` object the message `bar` with argument `baz` (in Kay's current terminology); or, in more 'mainstream' terminology, it looks for a method in the `bar` slot of `foo` and calls it with `baz`.
As far as I'm aware, this is a pretty core concept in OOP: if our code isn't organised along these lines, then we're writing non-OOP code which just-so-happens to use classes/prototypes/methods (in the same way that we can e.g. write OOP code in C which just-so-happens to use procedures/switch/etc.).
(Side caveat: I appreciate that I'm making some assumptions here; one of my pet peeves with OOP is that it's rife with No True Scotsman fallacies about what is "proper" OOP, e.g. see 90% of the content on https://softwareengineering.stackexchange.com )
There is a fundamental asymmetry between the roles of `foo` (object, receiver of message, container of methods) and `baz` (argument, included in message, passed into method).
A function call like `bar(foo, baz)` doesn't have this asymmetry: the order of the arguments is just a convenience, it has no effect on how the `bar` symbol is resolved (in most languages it's via lexical scope, just like every other variable). Swapping them is also trivial, e.g.:
In contrast, I can't think of an OOP alternative to this which isn't messy (e.g. adding a `rab` method to `baz` via monkey-patching).Of course, the elephant in the room here is CLOS, but that's sufficiently different from most OOP as-practiced that it's better considered separately (e.g. I'd be more inclined to agree that CLOS methods are "100% pure syntax vs a function call")
> Non-syntactic aspects are maybe a more involved discussion. For an example, I personally think traditional OO lends itself very nicely to runtime polymorphism.
I also disagree with this, again due to the artificial asymmetries that OOP introduces (that objects "contain" methods). In particular, as far as I'm aware OOP simply cannot express return-type polymorphism. The asymmetry between arguments and return values is certainly more fundamental than the completely unneeded distinction between receiver and arguments, but it's still very useful to dispatch on the output rather than the input.
The classic example is `toString` being trivial in OOP (a method which takes some object as input and renders it to a string; the implementation dispatches on the given object); whilst `fromString` is hard (a method which takes a string and returns some object parsed from it; OOP can't dispatch the implementation by "looking inside" the object, since we don't have an object until we've finished parsing).