Hacker News new | past | comments | ask | show | jobs | submit login
Organizing Programs Without Classes (1991) [pdf] (selflanguage.org)
27 points by sillysaurusx on April 22, 2021 | hide | past | favorite | 6 comments



For people who aren't familiar with Self (which is a pity), you can think of it as "if JavaScript supported multiple prototypes": fields whose name end in * all act like __proto__ (trying one after another) and the "lobby" is equivalent to the global this object (known to browser users as "window"). Many of the other semantics are the same, though the syntax is very very different... FWIW, Smalltalk's syntax maintained a lot of similarity to Self, and both share the penchant for replacing control structure with dispatch, including for basic things like loops and if statements; for people who haven't studied Smalltalk at all (another pity), Ruby is "quite different" and yet "remarkably similar", and so is worth having in an adjacent mental space.


Is this the same as Rust's traits and trait objects?


It's closest in spirit to Steve Yegge's "Universal Design Pattern": http://steve-yegge.blogspot.com/2008/10/universal-design-pat...

Basically, the core data structure is a special kind of dictionary. When you fetch a key, if the dict doesn't have it, it recurses to a list of "parent" dictionaries that you can specify.

I don't know Rust's traits / trait objects, but the observation of the paper is that you can break apart your interfaces into a "traits dict".

E.g. dog traits would have bark and pant, and would have a parent link to animal traits, which has eat and sleep, which has a parent link to entity traits, which might have position / collision model / etc.

This lets you mix and match functionality at runtime, very similar to adding classes to Python's __bases__ list (which I'm quite find of for mixin purposes: https://github.com/shawwn/mtftorch/blob/8b680ab86603a7718044...)


> Basically, the core data structure is a special kind of dictionary. When you fetch a key, if the dict doesn't have it, it recurses to a list of "parent" dictionaries that you can specify.

Sounds like Lua metatables, as well as Python classes.

Also.. what's this Mftorch thing? Your own library?


Not the same, but there are similarities. The most obvious difference is that Self is a dynamic language whereas Rust is statically typed. In the Rust bibliography (https://rustc-dev-guide.rust-lang.org/appendix/bibliography....) you find a link to http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf which uses Smalltalk and also discusses the difference of their traits approach to Self. I guess they should also have added this reference: http://scg.unibe.ch/archive/papers/Nier05gTraitsCSharp.pdf, which demonstrated in 2005 how to add traits to a strictly/statically typed language.


I don't think so. Per the abstract:

> All organizational functions carried out by classes can be accomplished in a simple and natural way by object inheritance in classless languages, with no need for special mechanisms.

The paper replaces classical inheritance with prototypal inheritance, but it still relies on inheritance -- and more specifically, dynamic dispatch along an inheritance chain built into an object. Rust's trait system is much (much!) closer to Haskell's typeclasses, Scala's traits, and object algebras [0], all of which are essentially based on passing a set of operations on a type separately from that type itself (with various degrees of compile-time optimization and/or type-driven inference).

[0] https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf

Also from the paper:

> No special language features need to be added to support traits objects — a traits object is a regular object shared by all instances of the type using normal object inheritance. Since traits objects are regular objects, they may contain assignable data slots which are then shared by all instances of the data type, providing the equivalent of class variables.

Under static dispatch (the default), Rust's traits don't have the same recursive runtime chaining behavior. You can declare supertraits, but with static dispatch (the default) the supertrait methods are resolved at compile time anyway.

Under dynamic dispatch over "trait objects", I'm not sure about the details, but it would be reasonable to imagine that the "subtrait" contains a pointer for each supertrait. (Rather critically for Rust, trait implementations are defined statically and contain no data.) Trait objects in Rust do separate the instance data from the trait behavior in the same way that the paper suggests. The term "fat pointer" often comes up -- a trait object is a combination of two pointers, one to the instance data and one to the trait implementation for the instance. The paper refers to the trait implementation itself as the "traits object", but it's fair to call that a minor difference.

That said, the impl for a trait object can't be manually set or changed at runtime -- at least, not without unsafe arcane magic and some serious assumptions about codegen from rustc -- and Rust knows exactly what methods are available from a trait object statically, so there's no need for an inheritance-style chained dispatch. If you call a method on a trait, I'm pretty sure Rust will produce machine code that just traverses the trait impls directly to the known location of the pointer to the method you want to call. It doesn't have to check "do you know this method? no, do you know this method? no, ..."




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

Search: