35 ms·
How are you supposed to implement the concept of interface in non OOP languages?
by benob 2y ago
How are you supposed to implement the concept of interface in non OOP languages?
- virexene 2y agoIn Zig's case, you do what Rust/C++ do implicitly and create a table of function pointers
- masklinn 2y agoInterface or interface-adjacent features are present in numerous non-OO languages: ML modules, Haskell typeclasses, rust traits, go interfaces, …
- feverzsj 2y agoUsing function pointers like most c projects do.
- jimbob45 2y agoThere’s no excuse for C not to have some syntactic sugar around function pointers at this point. They’re miserable to use.
- ajb 2y agoIt does. You don't need to use & on a function or * on a function pointer, this happens automatically (see https://stackoverflow.com/questions/7518815/function-pointer-automatic-dereferencing https://stackoverflow.com/questions/7518815/function-pointer... ). I suppose the : operator from lua would be useful.
- fuhsnn 2y agoWith "typeof" standardized in C23 it kinda does the job: int(* fnptr_name)(int param) becomes typeof(int(int param))* fnptr_name There is a recent proposal to update standard headers to this style: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3450.txt https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3450.txt
- debugnik 2y ago-void (*signal(int sig, void (*func)(int)))(int); +typeof(void (int)) *signal(int sig, typeof(void (int)) *func); Much better honestly. The original took me a few reads to understand, until I noticed the outer (int) is part of the return type.
- jstimpfle 2y agoC syntax becomes hard to read once you nest function declarations (i.e. functions taking functions, taking functions...). But that's actually the case for most common syntaxes. Haskell type syntax is an exception, but that's a language where you're using lots of higher-order functions. For the most common use cases C syntax is quite ergonomic once you've learned the principle. // declare func as normal function void func(ARGUMENTS); // declare (*func) as normal function: func is a pointer to a normal function void (*func)(ARGUMENTS); // can also declare a type name instead of a function typedef void functype(ARGUMENTS); // and then use that to declare function-pointer functype *func; // can also declare a function pointer type directly (I don't normally do this) typedef void (*funcptrtype)(ARGUMENTS); funcptrtype func; // declare function pointer
- tialaramex 2y agoNotice that this choice, which I agree is popular in C software, has a perf overhead. I'll illustrate: Imagine there are two functions with the same signature dog_noise and goose_noise, and goose_noise needs to set up a Honk Apparatus but dog_noise does not, it can easily Bark without prior setup. Now suppose we want to use our own make_noise_six_times function, but we're going to pass in a function to say which noise. make_noise_six_times(dog_noise) and make_noise_six_times(goose_noise) With this function pointer approach, make_noise_six_times has no idea about the Honk Apparatus, it will just call into goose_noise six times, each time setting up and tearing down a Honk Apparatus. At runtime these are likely CALL instructions. However, in a language like Rust that's going to be mono-morphized, make_noise_six_times(dog_noise) and make_noise_six_times(goose_noise) end up generating two implementations which get optimised, there's a good chance the noise sub-functions are inlined - so no function calls - and the creation of the Honk Apparatus may get hoisted out of the loop for the make_noise_six_times(goose_noise) implementation, even though it's across function boundaries, so long as that obeys the "As if" rule. The reduction in overhead can be dramatic - if your inner functions are tiny the call overhead might dwarf what they actually do, so the inlining makes the whole program orders of magnitude faster in this case. This is very noticeable for e.g. sorting, since the comparison function is executed so often in a hot loop, if that's a C function call it's so much more expensive than if it's a single inlined CPU instruction.
- krab 2y agoYou can still have a full object that carries both state and function pointers.
- wavemode 2y agoI happen to be of the opinion that Rust programs tend to heavily overuse monomorphization. It's not always so clear cut that it's worth gaining a slight amount of runtime speed in exchange for losing a massive amount of compilation speed and binary size. What I'd love is a language which is able to compile 'impl TraitName' into dynamic dispatch in debug mode and only monomorphize it in release mode. Basically, an automation of this technique: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1055a2f0583c4c01e1386508bb506ee4 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- GuestHNUser 2y agoFunction pointers typically
- unclad5968 2y agoThe same way the OOP compilers implement them, with v-tables. Basically the compiler makes a table of function pointers so calls can be resolved (not sure resolved is the correct term) at runtime. In Zig or any other C like language without "interfaces", you would implement the V-table by hand, which is a common idiom in Zig.
- bsaul 2y agodo you have an example of what that would look like ? I'm a bit confused about when you would construct this table and how one would use it
- genewitch 2y agoI'm not sure what interface means but virtual table in C++ apparently for inheritance, virtual function, and polymorphism (which is a spell or something The rest of OOP is lipstick on arrays and arrays of arrays and "structs / records" or software defined arrays. In my opinion.
- frabert 2y agoLet's say you have an interface 'Reader' with methods 'Read' and 'Close'. In a world where no interfaces exist, every user of a different implementer of this interface would need to know exactly which 'Read' and 'Close' implementations to call, and possibly generate different code for it. In order to make this mechanism generic, you can instead say, "every implementer of the 'Reader' interface has a pointer as its first field, and that pointer leads to an array of two elements: the first element is a pointer to a 'Read' method, the second to a 'Close' method." This way, the user of a Reader knows nothing of the internals of each implementation, other than how to find its methods
- pjmlp 2y agoBasically you have something like typedef struct Writer { int (*method_one)(const char*, int); //... } Writer; void takes_a_writer(const Writer *obj) { // somewhere obj->method_one(buff, buff_size); // ... } // a method implementation int method_one_impl(const char *buff, int n) { //... } // somewhere else Writer w; w.method_one = method_one_impl; //... takes_a_writer(&w); It isn't the best example, but should do the job giving you an overview.
- Arch485 2y agoVTable structs. Instead of declaring `interface Foo { void bar(); }`, you do `struct Foo { this: void, bar: const fn (this: *void); }` (i.e. a struct with a function pointer field and a "this" pointer). This is how interfaces work under the hood in other languages. Note that the code I wrote is not any particular language; it's just demonstrative.
- donatj 2y agoI'd do it similar to how Go does it. Zig is heavily influenced by Go to begin with.
- aiono 2y agoNon-oop languages has interfaces for a long time: Rust's traits, Haskell's typesclasses etc.
- mrkeen 2y agoRebuttal, how do you implement interfaces in OOP languages in cases where you don't yet expect the object to exist, e.g. an interface with toString and fromString methods.
- caspper69 2y agoWith the interface keyword (java/c#). You just define the required methods and that's the interface.
- layer8 2y agoThink again about fromString, that would be a static constructor method. The GP is looking at metaclasses.
- caspper69 2y agoI'm not going to attempt to speak for java, but at least in C#, it supports virtual static methods in interfaces, including generics. So, as I stated, you would use the interface keyword. public interface IFromString<T> { static abstract T FromString(string value); } public struct MyHasFromString : IFromString<MyHasFromString> { public string Text = ""; public MyHasFromString(string value) { Text = value; } public static MyHasFromString FromString(string value) => return new MyHasFromString(value); public override string ToString() => Text; }
- deleted 2y ago[deleted]
- humanfromearth9 2y ago[flagged]
- hansvm 2y agoOne halfway option in Zig (though I _think_ interfaces are still being considered for addition to the language as first-class citizens) is to use the language's metaprogramming facilities. Somebody, once, in the userspace of the language, needs to write a utility that reads a type and produces an interface checker for that type, so that you're able to write code like the following: const IDog = Checker(TemplateDogType); Then you can use that when defining a function expecting to conform to some interface: fn bark(_dog: anytype) void { const dog: IDog(_dog) = _dog; dog.bark(); } You can easily get nice compiler errors, no runtime overhead, and all the usual sorts of things you expect from a simple interface system. It's just more verbose. Limiting access to non-interface methods without runtime overhead would be a bit more cumbersome I think. Off the top of my head, the following API is possible though: fn bark(dog: anytype) void { IDog.bark(&dog); }
- cgh 2y agoI'm not sure I understand. Anytype is type-checked at compile time, not runtime, so you already have these things. The downside of anytype is that it's non-documenting, in the sense that you can't read the function signature and know what's expected.
- hansvm 2y agoThe thing you gain is exactly that missing documentation (via @compileError and whatnot in the hypothetical library code I hand-waved away). The compiler errors can point you to the exact interface you're supposed to adhere to (as opposed to combatting errors one at a time), and by construction give you a non-out-of-date template to examine. It's not perfect since it's all in the userspace of the language (it'd be nicer to be able to express an interface type in the function signature), but it solves the problem you mentioned completely.
- feelamee 2y agoconcept interface isn't owned or invented by OOP.