4 ms·
Using C, which doesn't even have closures, as an example of why this doesn't work is rather silly. How can you have closure-based classes without closures? The
by sirwhinesalot 5y ago
Using C, which doesn't even have closures, as an example of why this doesn't work is rather silly. How can you have closure-based classes without closures? The ergonomics are beyond horrific in this case but I don't see how that's relevant.
> And yet, there are approximately 0 examples of the "closure-backed classes" example you give, while every vaguely popular language except C has some variant of polymorphic classes, from StandardML to Lisp.
SML is actually one of the best languages to implement this approach! And as far as I know this is even the recommended way to do it (see the MLton docs). No idea what polymorphic classes you're thinking of when it comes to SML. Nobody uses objects in OCaml either.
> Your closure example is not used in any langauge because it's extremely un-ergonomic.
I'm not arguing ergonomics, just implementation details, you can add all the syntax sugar you want :)
Also Erlang Actors are functions with an explicit message loop. So that's even lower level than this.
> If the langauge doesn't have some kind of concept of "interface" (abstract class, type class, trait, structural typing etc), then you'll either miss polymorphism (return a single specific type), or be too dynamic (return an object that could do anything).
I don't follow, neither of those are true. Yes you return a single type, that single type is the interface type (which can have infinite implementations, just like in any language where you return an interface). But you don't need a specific concept of an interface, it's just a record of closures.
Also every language should have a way to say "this struct is the union of these structs", which gives you the subtyping you're typically missing with this approach (i.e. an object that implements N "interfaces" can be cast to all of them).
Without that you need to add an extra layer of indirection with explicit casting functions. Not ergonomic as you say.
> It also risks being inefficient if you try to naively add function pointer members to each object to support runtime dispatch.
If you're not using function pointers then you don't have runtime polymorphism, what do you think a vtable is? It's a struct of function pointers. An object is a pair (voidptr, vtableptr), where the vtable can be created at compile time or at program start. Of course that's not very ergonomic, but I'm not arguing ergonomics.
> The closure-based alternative also obscures the relationship between product data types (structs/record types) and classes (structs with methods).
Classes obscure the relationship between subtyping, interfaces, inheritance, abstract datatypes and modules. A class is much more than a struct with methods. A struct with methods is vtable.
> Closure-backed classes are a neat experiment, but just like manual virtual table management in C, they are not a realistic solution to the problems that classes address, if you can possibly avoid them.
What problems do classes address? Nobody seems to have an answer to this, which is why modern languages like Rust and Go don't have them. You can argue that traits and interfaces are are "OO", but they're not classes. Rust doesn't let me do Iterable.new{...} at runtime, exposing it's nature as a struct of functions, but nothing says it couldn't.
- tsimionescu 5y agoA class is any construct that can: - expose multiple function (implementations) as a single unit - be chosen at runtime as a representation of a contract (polymorphism) - (optionally) encapsulate some state accessible only to the functions of the class This construct comes up naturally in a wide variety of programming tasks, mainly related to the large-scale organization of code. Here are some examples of language constructs that are classes (perhaps by other names): - `class` in C++, Java, C#, OCaml etc.; polymorphism achieved through interfaces/pure-virtual classes - `class` in Python, Ruby; polymorphism achieved through dynamic dispatch - `defmethod`+`defclass` in Common Lisp CLOS; polymorphism achieved through `defgeneric` (a rare example where polymorphism is at the single function level, instead of a bundle of functions) - `structure`&functors in SML; polymorphism is achieved through `signature` - `module` in OCaml; polymorphism achieved through the use of `module types` - `struct` in Go; polymorphism achieved through use of `interface` - `struct` in Rust; polymorphism achieved through `trait` - `data` in Haskell; polymorhpism achieved through `class` and `instance` - `typename` in the C++ template meta-language; polymorphism achieved through dynamic typing (SFINAE) or `concepts` Now, some of these languages/constructs may have additional properties. Some support subtyping, some support multiple fields etc. Some explicitly implement interfaces, some implement them implicitly. But they all have the initial three properties in common, and recognizably solving the exact same problem in slightly different ways. The implementation varies quite significantly, but I would be surprised if even one of these languages actually implements this construct using closures. Now, to address some of the other points: > If you're not using function pointers then you don't have runtime polymorphism, what do you think a vtable is? I have often seen proposals to implement runtime dispatch via something like: struct Obj { foo: fn () -> int bar: fn int -> int field1: int } This is wildly inefficient compared to the vtable approach, which is closer to struct ClassA { //vtable foo: fn () -> int bar: fn int -> int } struct Obj { vtable: &ClassA field1: int } > But you don't need a specific concept of an interface, it's just a record of closures. If your language doesn't have a concept like Interface to begin with, you can't implement it out of thin air. There are few languages that don't have this. C is perhaps the only mainstream example, and indeed in C (even if you had closures) you couldn't return an object from a closure that is not a specific concrete type; or void* and expect people to cast to something else. If your language does have interfaces, it will also have a better way to define classes corresponding to those interfaces than random closures returning interface types. > I'm not arguing ergonomics, just implementation details, you can add all the syntax sugar you want I've mentioned this before, but I don't know of a single language where the class mechanism is actually implemented in terms of closures. Typically there are other specialized constructs at the compiler level used specifically for this.