They definitely distance themselves from inheritance. However both Rust traits and Go interfaces look to me like they're there to appease people who like the .method() calling syntax. (I know there's more to the story than that, particularly for Rust and its type system...) I think either could've gone with multimethods or overloaded functions and have been better for it, but a lot of people seem to really like the object.method() look.
There’s a pretty simple reason for that: autocomplete.
In most halfway decent dev environments, typing “object.” will present you with a list of possible operations on that object. OTOH, if you want to do “method(object)” you need to know the method name (in all current scopes, including globals).
I’m not saying this is the only or even best way to write code, but it’s definitely a factor IMHO.
result foo(bar b, other stuff)
result foo(baz b, other stuff)
result foo(bum b, other stuff)
To: result r = bar.foo(other stuff)
result r = baz.foo(other stuff)
result r = bum.foo(other stuff)
Not because of the IDE, but because the compiler error messages are horrible for the ones above when you make a mistake. If you pass the type as the first argument, the compiler "helpfully" tells you 3 pages of information about all of the overloads. However, if you use method syntax, it only tells you overloads for the one object.It's a little frustrating that the hammer is changing the shape of our hands, and not vice versa.
The problem I have with most language's function call syntax (except for point-free stack based languages like FORTH and PostScript) is that you can have multiple fingers on your "in" hand, but only one finger on your "out" hand. C#'s in/out/ref modifiers and Lisp's multiple-value-bind are hacks.
https://en.wikipedia.org/wiki/Tacit_programming
FORTH's /MOD ( numerator denominator -- remainder quotient ) naturally takes two integer inputs and returns two integer outputs, and it doesn't use need any special clumsy syntax to express that.
I dunno, in many modern languages that aren't point-free stack-based languages you can (and the difference between these is one of perspective more than concrete substance, arguably) either have multiple “fingers” on either hand or only one on each, but the one value each touches can be arbitrarily structured and destructured.
There's a big difference between simply and efficiently returning multiple values on the stack without generating intermediate garbage and memory references, and packing multiple values up into a single tuple, polymorphic array, structure, or class, and then destructuring it later, or passing input parameters that are indirect pointers to temporary output locations in linear memory.
Using indirect pointers for output parameters can also cause bugs and performance optimization problems with aliasing.
https://en.wikipedia.org/wiki/Pointer_aliasing
To elaborate what I said: C#'s in/out/ref and Lisp's multiple-value-bind syntax are clumsy, inelegant, and inefficient hacks.
And languages like Java that don't have pointers can't even do that, and just have to proliferate pointless container classes and generate garbage intermediate objects. Quick: How do you implement swap(a, b) in Java? (Without using an IntermediatingObjectSwapperDependencyInjector- ProxyThunkingShadowEnumeratorComponentBeanAdaptor- DecoratorReferenceRepositoryServiceProviderFactoryFactory!)
http://www.javapractices.com/topic/TopicAction.do?Id=37
https://stackoverflow.com/questions/1403921/output-parameter...
https://stackoverflow.com/questions/3624525/how-to-write-a-b...
With WebAssembly, for example, returning multiple values has a significant effect on performance and code size, because it's much more costly to return multiple values indirectly through linear memory than on the stack. (The stack is in a separate address space that you can't point to like linear memory.)
That's why there's an active multiple value return proposal for WebAssembly, which is implemented in Chromium release 80:
https://www.chromestatus.com/feature/5192420329259008
https://hacks.mozilla.org/2019/11/multi-value-all-the-wasm/
>But Why Should I Care?
>Code Size
>There are a few scenarios where compilers are forced to jump through hoops when producing multiple stack values for core Wasm. Workarounds include introducing temporary local variables, and using local.get and local.set instructions, because the arity restrictions on blocks mean that the values cannot be left on the stack.
>Consider a scenario where we are computing two stack values: the pointer to a string in linear memory, and its length. Furthermore, imagine we are choosing between two different strings (which therefore have different pointer-and-length pairs) based on some condition. But whichever string we choose, we’re going to process the string in the same fashion, so we just want to push the pointer-and-length pair for our chosen string onto the stack, and control flow can join afterwards. [...]
>This encoding is also compact: only sixteen bytes!
>When we’re targeting core Wasm, and multi-value isn’t available, we’re forced to pursue alternative, more convoluted forms. We can smuggle the stack values out of each if and else arm via temporary local values: [...]
>This encoding requires 30 bytes, an overhead of fourteen bytes more than the ideal multi-value version. And if we were computing three values instead of two, there would be even more overhead, and the same is true for four values, etc… The additional overhead is proportional to how many values we’re producing in the if and else arms. [...]
>Returning Small Structs More Efficiently
>Returning multiple values from functions will allow us to more efficiently return small structures like Rust’s Results. Without multi-value returns, these relatively small structs that still don’t fit in a single Wasm value type get placed in linear memory temporarily. With multi-value returns, the values don’t escape to linear memory, and instead stay on the stack. This can be more efficient, since Wasm stack values are generally more amenable to optimization than loads and stores from linear memory.
Some languages do (more if you consider things that support what looks like multiple arguments but which the complete set of arguments that can be passed to a function corresponds directly to a single data structure in the language.)
> There's a big difference between simply and efficiently returning multiple values on the stack without generating intermediate garbage and memory references, and packing multiple values up into a single tuple
Fundamentally, there's not, since you have to represent both the number of items and each item either way. It's true that there are more and less efficient means of performing the task, but the information required is identical, so it is quite possible for any method capable of doing what looks like one to a user of the language to implement what looks like the other from the same perspective.
The obvious implementation when conceptualized each way given other elements of a language or it's implementation design may be different, but that's not an inherent difference, and implementing efficiencies in the implementation has no necessary reflection in language-level features (and supporting any particular language-level feature isn't a guarantee of efficient implementation.)
I think Python's way isn't amazing, but it's not too bad either:
d, r = divmod(100, 3)
It's a bit uglier in C++, but you could make this work: int d, r;
refs(d, r) = divmod(100, 3);
I'm certain there's either a Boost or STL library for this. (I wrote my own for C++98 at one point)It's clear you like stack based languages, and I certainly admit they can be concise.
Multi-Value All the Wasm (hacks.mozilla.org)
the "hammer" in this case being "doing things in causal order" - hard to find a different tool in the shed.
A._vtable[f](A, B)
It really is just a function of two arguments, even when the privileged first argument looks like it's outside of the parens.I mean that if you want contextual autocompletion (e.g. have only the relevant functions being shown given the type of an argument) then at some point you have to give your IDE this type information before typing the function - however you look at it it will always be [context] [function] instead of [function] [context].
> A.f(B) is just a syntax sugar around f(A, B) unless dynamic dispatch comes into play.
this is a complete implementation detail of your operating system ABI and the way OO languages compile down to it, and in no way a "general truth" (of course there is an equivalence between both). There used to be some CPUs with hardware instructions for OO method calls in the 80s for instance - you may want to call that syntax sugar around electrons, but I would respectfully disagree :-)
(even then, for instance Microsoft has the __thiscall calling convention for C++ methods which differs from __cdecl used for C functions - so even there there are fundamental differences between a.f(b) and f(a, b) as arguments will be passed differently to the method.)
However, I think using methods to provide infix syntax is a real mistake. For instance, if I want to make an infix operator which allows me to subtract mytype and yourtype, I can do the following:
mytype - yourtype; // mytype.sub(yourtype)
However, the opposite requires me to add methods to yourtype: yourtype - mytype; // yourtype.sub(mytype)
Python gets around this by having 'r' versions of functions which get invoked if yourtype refuses to acknowledge my type. However Go and Rust could resolve which binary function to use statically, but they don't want to support overloading unless it's a method on the first object.Another interesting asymmetry, which I think shows a weird favoritism for method syntax is that Rust allows both of these:
object_of_type_A.foo()
object_of_type_B.foo()
But not both of these: foo(object_of_type_A)
foo(object_of_type_B)
Ignoring dynamic/static dispatch for the moment, you can overload if you use method syntax, but not if you use function syntax. For me, I would prefer the latter, but I think I'm clearly in the minority.I don't think this is true. Objects can message themselves as part of base-class code; implementation inheritance means that this has to involve a level of indirection and dispatch, not just explicit delegation.
or maybe Erlang is its own thing and OOP -as what most people are thinking when talking about it- is the set of practices followed in languages descended from the cross between C and Smalltalk.
This explains how every introductory course in OOP using either Java, C#, Python, etc. will list some sort of OOP principles always including something along "objects talk to each other though message passing" which is hand-waved simply as something to do with taking other objects by ref
// taking a reference
bar.someMethod(foo)
Or some form of import method import Foo;
class Bar {
someMethod() {
// init the import
foo = new Foo()
// ... do something with it
foo.someMethod()
}
}
which is worse since you need a huge disclaimer to explain that in practice you need to account for Dependency Injection and something for Mocking.It is a matter of actually learning about OOP concepts in general, and Kant features in particular.
Swift's protocol oriented programming actually goes back to Objective-C protocols, Java's inspiration for interface types or Smalltalk traits (post-Smalltalk-80).
What most people think of when they think OOP is C++, Java, and Python which got OOP completely wrong and ruined our perception of OOP. They also tried to use their misshapen OOP hammer on every problem they could find to the point that it’s an overused meme. These days you see languages actively trying to distance themselves from OOP as a form of enticement (Rust and Go being the two prime examples).