├── LICENSE ├── barebones.c ├── barebones-combined.c ├── barebones-macro.c ├── name-constrained.c └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TotallyNotChase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /barebones.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* The `Show` typeclass allows types to be turned into their string representation */ 6 | typedef struct 7 | { 8 | char* (*const show)(void* self); 9 | } ShowTC; 10 | 11 | typedef struct 12 | { 13 | void* self; 14 | ShowTC const* tc; 15 | } Show; 16 | 17 | /* Polymorphic printing function */ 18 | void print(Show showable) 19 | { 20 | char* const s = showable.tc->show(showable.self); 21 | puts(s); 22 | free(s); 23 | } 24 | 25 | 26 | /* A very holy enum */ 27 | typedef enum 28 | { 29 | holy, 30 | hand, 31 | grenade 32 | } Antioch; 33 | 34 | static inline char* strdup_(char const* x) 35 | { 36 | char* const s = malloc((strlen(x) + 1) * sizeof(*s)); 37 | strcpy(s, x); 38 | return s; 39 | } 40 | 41 | /* The `show` function implementation for `Antioch*` */ 42 | static char* antioch_show(Antioch* x) 43 | { 44 | /* 45 | Note: The `show` function of a `Show` typeclass is expected to return a malloc'ed value 46 | The users of a generic `Show` are expected to `free` the returned pointer from the function `show`. 47 | */ 48 | switch (*x) 49 | { 50 | case holy: 51 | return strdup_("holy"); 52 | case hand: 53 | return strdup_("hand"); 54 | case grenade: 55 | return strdup_("grenade"); 56 | default: 57 | return strdup_("breakfast cereal"); 58 | } 59 | } 60 | 61 | /* The wrapper function around `antioch_show` */ 62 | static inline char* antioch_show__(void* self) 63 | { 64 | return antioch_show(self); 65 | } 66 | 67 | /* Make function to build a generic `Show` out of a concrete type- `Antioch` */ 68 | Show prep_antioch_show(Antioch* x) 69 | { 70 | /* Build the vtable once and attach a pointer to it every time */ 71 | static ShowTC const tc = { .show = antioch_show__ }; 72 | return (Show){ .tc = &tc, .self = x }; 73 | } 74 | 75 | int main(void) 76 | { 77 | Show const antsh = prep_antioch_show(&(Antioch){ grenade }); 78 | print(antsh); 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /barebones-combined.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* The `Show` typeclass allows types to be turned into their string representation */ 6 | typedef struct 7 | { 8 | char* (*const show)(void* self); 9 | } ShowTC; 10 | 11 | typedef struct 12 | { 13 | void* self; 14 | ShowTC const* tc; 15 | } Show; 16 | 17 | 18 | /* The `Enum` typeclass allows types to be enumerable */ 19 | typedef struct 20 | { 21 | int (*const from_enum)(void* self); 22 | } EnumTC; 23 | 24 | typedef struct 25 | { 26 | void* self; 27 | EnumTC const* tc; 28 | } Enum; 29 | 30 | 31 | /* Typeclass that asks for both `Show` and `Enum` implementation */ 32 | typedef struct 33 | { 34 | void* self; 35 | ShowTC const* showtc; 36 | EnumTC const* enumtc; 37 | } ShowEnum; 38 | 39 | void print_shen(ShowEnum shen) 40 | { 41 | char* const s = shen.showtc->show(shen.self); 42 | int enm = shen.enumtc->from_enum(shen.self); 43 | printf("%s : %d\n", s, enm); 44 | free(s); 45 | } 46 | 47 | /* The `show` function implementation for `int` */ 48 | static char* int_show(int* x) 49 | { 50 | /* 51 | Note: The `show` function of a `Show` typeclass is expected to return a malloc'ed value 52 | The users of a generic `Show` are expected to `free` the returned pointer from the function `show`. 53 | */ 54 | size_t len = snprintf(NULL, 0, "%d", *x); 55 | char* const res = malloc((len + 1) * sizeof(*res)); 56 | snprintf(res, len + 1, "%d", *x); 57 | return res; 58 | } 59 | 60 | /* The wrapper function around `int_show` */ 61 | static inline char* int_show__(void* self) 62 | { 63 | return int_show(self); 64 | } 65 | 66 | /* Make function to build a generic `Show` out of a concrete type- `int` */ 67 | Show int_to_show_inst(int* x) 68 | { 69 | /* Build the vtable once and attach a pointer to it every time */ 70 | static ShowTC const tc = { .show = int_show__ }; 71 | return (Show){ .tc = &tc, .self = x }; 72 | } 73 | 74 | /* The `from_enum` function implementation for `int` */ 75 | static int int_from_enum(int* x) 76 | { 77 | return *x; 78 | } 79 | 80 | /* The wrapper function around `int_from_enum` */ 81 | static inline int int_from_enum__(void* self) 82 | { 83 | return int_from_enum(self); 84 | } 85 | 86 | /* Make function to build a generic `Show` out of a concrete type- `int` */ 87 | Enum int_to_enum_inst(int* x) 88 | { 89 | /* Build the vtable once and attach a pointer to it every time */ 90 | static EnumTC const tc = { .from_enum = int_from_enum__ }; 91 | return (Enum){ .tc = &tc, .self = x }; 92 | } 93 | 94 | int main(void) 95 | { 96 | int x = 42; 97 | ShowEnum shen = { .self = &x, .showtc = int_to_show_inst(&x).tc, .enumtc = int_to_enum_inst(&x).tc }; 98 | print_shen(shen); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /barebones-macro.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define CONCAT_(a, b) a##b 6 | #define CONCAT(a, b) CONCAT_(a, b) 7 | 8 | /* The `Show` typeclass allows types to be turned into their string representation */ 9 | typedef struct 10 | { 11 | char* (*const show)(void* self); 12 | } ShowTC; 13 | 14 | typedef struct 15 | { 16 | void* self; 17 | ShowTC const* tc; 18 | } Show; 19 | 20 | #define impl_show(T, Name, show_f) \ 21 | static inline char* CONCAT(show_f, __)(void* self) \ 22 | { \ 23 | char* (*const show_)(T* self) = (show_f); \ 24 | (void)show_; \ 25 | return show_f(self); \ 26 | } \ 27 | Show Name(T* x) \ 28 | { \ 29 | static ShowTC const tc = { .show = (CONCAT(show_f, __)) }; \ 30 | return (Show){ .tc = &tc, .self = x }; \ 31 | } 32 | 33 | /* Polymorphic printing function */ 34 | void print(Show showable) 35 | { 36 | char* const s = showable.tc->show(showable.self); 37 | puts(s); 38 | free(s); 39 | } 40 | 41 | 42 | /* A very holy enum */ 43 | typedef enum 44 | { 45 | holy, 46 | hand, 47 | grenade 48 | } Antioch; 49 | 50 | static inline char* strdup_(char const* x) 51 | { 52 | char* const s = malloc((strlen(x) + 1) * sizeof(*s)); 53 | strcpy(s, x); 54 | return s; 55 | } 56 | 57 | /* The `show` function implementation for `Antioch*` */ 58 | static char* antioch_show(Antioch* x) 59 | { 60 | /* 61 | Note: The `show` function of a `Show` typeclass is expected to return a malloc'ed value 62 | The users of a generic `Show` are expected to `free` the returned pointer from the function `show`. 63 | */ 64 | switch (*x) 65 | { 66 | case holy: 67 | return strdup_("holy"); 68 | case hand: 69 | return strdup_("hand"); 70 | case grenade: 71 | return strdup_("grenade"); 72 | default: 73 | return strdup_("breakfast cereal"); 74 | } 75 | } 76 | 77 | /* Make function to build a generic `Show` out of a concrete type- `Antioch` */ 78 | impl_show(Antioch, prep_antioch_show, antioch_show) 79 | 80 | int main(void) 81 | { 82 | Show const antsh = prep_antioch_show(&(Antioch){ hand }); 83 | print(antsh); 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /name-constrained.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define CONCAT_(x, y) x ## y 6 | #define CONCAT(x, y) CONCAT_(x, y) 7 | 8 | /* Consistently name the impl functions */ 9 | #define ImplName(T, TypeclassName) CONCAT(CONCAT(T, _to_), CONCAT(TypeclassName, _inst)) 10 | 11 | /* "Apply" a typeclass over a concrete type */ 12 | #define ap(x, T, TypeclassName) ImplName(T, TypeclassName)(x) 13 | 14 | /* The `Show` typeclass allows types to be turned into their string representation */ 15 | typedef struct 16 | { 17 | char* (*const show)(void* self); 18 | } ShowTC; 19 | 20 | typedef struct 21 | { 22 | void* self; 23 | ShowTC const* tc; 24 | } Show; 25 | 26 | #define impl_show(T, show_f) \ 27 | static inline char* CONCAT(show_f, __)(void* self) \ 28 | { \ 29 | char* (*const show_)(T* self) = (show_f); \ 30 | (void)show_; \ 31 | return show_f(self); \ 32 | } \ 33 | Show ImplName(T, Show)(T* x) \ 34 | { \ 35 | static ShowTC const tc = { .show = (CONCAT(show_f, __)) }; \ 36 | return (Show){ .tc = &tc, .self = x }; \ 37 | } 38 | 39 | /* Polymorphic printing function */ 40 | void print(Show showable) 41 | { 42 | char* s = showable.tc->show(showable.self); 43 | puts(s); 44 | free(s); 45 | } 46 | 47 | 48 | /* A very holy enum */ 49 | typedef enum 50 | { 51 | holy, 52 | hand, 53 | grenade 54 | } Antioch; 55 | 56 | static inline char* strdup_(char const* x) 57 | { 58 | char* s = malloc((strlen(x) + 1) * sizeof(*s)); 59 | strcpy(s, x); 60 | return s; 61 | } 62 | 63 | /* The `show` function implementation for `Antioch*` */ 64 | static char* antioch_show(Antioch* x) 65 | { 66 | /* 67 | Note: The `show` function of a `Show` typeclass is expected to return a malloc'ed value 68 | The users of a generic `Show` are expected to `free` the returned pointer from the function `show`. 69 | */ 70 | switch (*x) 71 | { 72 | case holy: 73 | return strdup_("holy"); 74 | case hand: 75 | return strdup_("hand"); 76 | case grenade: 77 | return strdup_("grenade"); 78 | default: 79 | return strdup_("breakfast cereal"); 80 | } 81 | } 82 | 83 | /* Make function to build a generic `Show` out of a concrete type- `Antioch` */ 84 | impl_show(Antioch, antioch_show) 85 | 86 | int main(void) 87 | { 88 | print(ap(&(Antioch){ holy }, Antioch, Show)); 89 | return 0; 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polymorphism through Typeclasses / Interface / Traits 2 | Ideas, thoughts, and notes on an action based polymorphism pattern for good ol' C. Originally used in [c-iterators](https://github.com/TotallyNotChase/c-iterators), and explained in a [small document](https://github.com/TotallyNotChase/c-iterators/blob/master/Typeclass%20Pattern.md). 3 | 4 | This is meant to be an extension (alongside some fixes) to the aforementioned document. In reality, this pattern was supposed to be a major focus point of c-iterators. But I realized that I needed another repo to log my ideas and thoughts about this pattern. 5 | 6 | You're free to use this pattern and the related ideas discussed in this repository. Although, there is a [LICENSE](./LICENSE) file, I don't expect people to have to include it everywhere. Attribution is all I ask for. 7 | 8 | # A brief introduction 9 | Before we move on to implementations, I expect you to be familar with *action based polymorphism*. For the OO programmer, this is an [Interface](https://docs.oracle.com/javase/tutorial/java/concepts/interface.html). For the Functional programmer, this is a [Type class](https://en.wikipedia.org/wiki/Type_class). Though in reality, the implementation is more akin to [Trait objects](https://doc.rust-lang.org/book/ch17-02-trait-objects.html) than real typeclasses. 10 | 11 | # Goals 12 | * Type safety - try not to make "user facing" interfaces (i.e concrete implementations) use `void*`. 13 | * **Full**, and **strict** standard C[1] conformance - no hacky strict alias violating shenanigans. 14 | * Extensible and usable in libraries (unlike `_Generic`) - possible through dynamic dispatch. 15 | * Open to being used with existing C libraries. Implementing typeclasses should not have special requirements. 16 | * As transparent as possible, especially from a usage perspective. 17 | * Base polymorphism around actions (abilities), not objects. 18 | 19 | [1] Core idea supports C90; examples use compound literals (C99) for convenience (not required); further (highly optional) abstractions may require C99 or even C11 20 | # Core Idea 21 | *Reference code: [barebones.c](./barebones.c)* 22 | 23 | A struct that contains 2 members- 24 | * The concrete data, to use with the respective functions (abilities of the type) - `self` 25 | * A vtable containing function pointers to the **exact implementations** of respective abilities for the specific type - `tc` 26 | 27 | Feel free to name these members whatever you'd like. I used `self`, since it's widely used in this context, and `tc`, for "type class". 28 | 29 | This struct **has** to be polymorphic. As in, the `self` member should be of type `void*`. The bonafide polymorphic type in C. This way, functions can simply ask for this struct to **constrain** types to ones that can *do certain actions*. The function can then call whatever function they need to call through the `tc` member, and pass in the `self` member. 30 | 31 | This is what that'd look like in psuedocode- 32 | ```c 33 | typedef struct typeclass_name_vtable 34 | { 35 | ReturnType (*const func_name)(void* self, ...); 36 | ... /* More "abilities" */ 37 | } TypeclassName_vtable; 38 | 39 | typedef struct typeclass_name 40 | { 41 | void* self; 42 | TypeclassName_vtable const* tc; 43 | } TypeclassName; 44 | ``` 45 | The latter struct is called a **Typeclass Instance**. 46 | 47 | A polymorphic function could then look like- 48 | ```c 49 | void poly_foo(TypeclassName x) 50 | { 51 | /* Use x's abilities here */ 52 | x.tc->func_name(x.self, ...); 53 | /* ... */ 54 | } 55 | ``` 56 | 57 | Coming back to the real world, you need to actually be able to turn concrete types into a typeclass. For that, you first need the concrete implementations for the abilities required by the typeclass. Once again, this is what that'd be like in psuedocode- 58 | ```c 59 | /* Assume `T` is a concrete type */ 60 | typedef some_type T; 61 | 62 | /* The `func_name` ability (shown above) impl for `T` */ 63 | ReturnType T_func_name(T* self, ...); 64 | ``` 65 | 66 | Assuming this is the only ability required by a certain typeclass, you can now make a function to convert `T` to that typeclass- 67 | ```c 68 | TypeclassName T_to_TypeclassName_inst(T* x) 69 | { 70 | static TypeclassName_vtable const tc = { .func_name = T_func_name }; 71 | return (TypeclassName){ .tc = &tc, .self = x }; 72 | } 73 | ``` 74 | However, `T_func_name`'s type is not compatible with the `func_name` member. Specifically, the typeclass member functions use `void* self`, but the user facing implementations **should** use `T* self` for the promised type-safety. The fix? A wrapper function- 75 | ```c 76 | static inline ReturnType T_func_name__(void* self, ...) 77 | { 78 | return T_func_name(self, ...); 79 | } 80 | ``` 81 | **NOTE**: The ellipsis (`...`) here does not denote variable arguments - it simply is a placeholder for more arguments. In real implementations, this should be replaced with the exact arguments of the expected functions. 82 | 83 | Now, inside `T_to_TypeclassName_inst`, you can assign `T_func_name__` to `func_name`. Because void pointer conversions are special cased by the standard, and are always redundant (cast to `void*`, cast back to original - valid and reliable) - this wrapper function will always be compliant and present no traps as long as it calls the correct function - a predicate that can be **guaranteed** by further restrictions, discussed in the [for-a-few-macros-more](#for-a-few-macros-more) section. 84 | 85 | One such wrapper function must be defined for every typeclass function. The sole goal of these wrapper functions is to simply allow the concrete, type-safe, user provided function to be called with a `void* self` argument. All arguments except `self` **must be the exact same**. 86 | 87 | For typeclass functions that have a return type of `void` - the wrapper function should merely call the user provided function, not return its value. As that would be semantically invalid. 88 | 89 | `T_to_TypeclassName_inst` is called the **Implementation Function**. As expected, it accepts a pointer to that concrete type (since it has to be assignable to `void*`), wraps it around its typeclass instance, and returns it. The **lifetime** of the returned struct is the *same as the lifetime of the data pointed to by the given pointer*. 90 | 91 | In general, neither this typeclass struct, nor the typeclass functions should take ownership of the concrete type. Though this isn't a forced requirement, just a suggested one. 92 | 93 | That's all there is to it! *This* is the typeclass pattern (or interface pattern). These typeclass structs can be used in libraries to have polymorphic functions. The correct functions will be dynamically dispatched to. 94 | 95 | **The code snippets above are all psuedocode for a general idea. This core idea is used to define and implement a [`Show`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Text-Show.html#t:Show) typeclass for an `enum` in [barebones.c](./barebones.c).** 96 | 97 | # Combining multiple typeclasses/interfaces 98 | *Reference code: [barebones-combined.c](./barebones-combined.c)* 99 | 100 | In real world code, you'll require types that implement multiple typeclasses/interfaces. You can encode that by having a struct containing the usual `self` member, and *multiple* vtables - each corresponding to a specific typeclass. 101 | 102 | Consider 2 typeclasses- [`Show`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Text-Show.html#t:Show) and [`Enum`](https://hackage.haskell.org/package/base-4.15.0.0/docs/GHC-Enum.html), their vtable structs looks like- 103 | ```c 104 | typedef struct 105 | { 106 | char* (*const show)(void* self); 107 | } ShowTC; 108 | 109 | typedef struct 110 | { 111 | int (*const from_enum)(void* self); 112 | } EnumTC; 113 | ``` 114 | 115 | You can combine these with- 116 | ```c 117 | typedef struct 118 | { 119 | void* self; 120 | ShowTC const* showtc; 121 | EnumTC const* enumtc; 122 | } ShowEnum; 123 | ``` 124 | Now, you have a typeclass instance - that requires *multiple typeclass implementations*. You'd wrap a type into this *combined typeclass instance* by obtaining the `Show` and `Enum` typeclass implementations *for that type* (by calling the implementation functions), extracting the vtables from those instances and putting them into this struct. 125 | 126 | If you implemented `Show` for `int`, and named the *implementation function* `int_to_show_inst`, implemented `Enum` for `int`, and named the *implementation function* `int_to_enum_inst`, this whole process would look like- 127 | ```c 128 | int x = 42; 129 | ShowEnum shen = { .self = &x, .showtc = int_to_show_inst(&x).tc, .enumtc = int_to_enum_inst(&x).tc }; 130 | ``` 131 | Feel free to generalize this into a function. This concept is showcased in [barebones-combined.c](./barebones-combined.c). 132 | 133 | You now have **full**, **type safe**, and **flexible** *polymorphism*. Usable *in any context* where you can use regular types. **Function arguments**, **container elements**, **polymorphic return values** etc. Many of these polymorphic types will be the combination of many typeclasses. Which may feel somewhat dry, and repetitive. However, the core idea is intentionally barebones. You can always design macros around these to make it less dry. Or, you may choose to simply have this completely transparent, your choice. 134 | 135 | # For a few macros more 136 | *Reference code: [barebones-macro.c](./barebones-macro.c)* 137 | 138 | This pattern, as implemented with maximum transparency above, may seem rather unintuitive for the implementor. It's very easy for the implementor of a typeclass to make mistakes in the way showcased above. You can, instead, make a macro to generalize the implementation- 139 | ```c 140 | #define CONCAT_(a, b) a##b 141 | #define CONCAT(a, b) CONCAT_(a, b) 142 | 143 | #define impl_TypeclassName(T, Name, func_name_f) \ 144 | ReturnType CONCAT(func_name_f, __)(void* self, ...) \ 145 | { \ 146 | ReturnType (*const func_name_)(T* self, ...) = (func_name_f); \ 147 | (void)func_name_; \ 148 | return func_name_f(self, ...); \ 149 | } \ 150 | TypeclassName Name(T* x) \ 151 | { \ 152 | static TypeclassName_vtable const tc = { .func_name = (CONCAT(func_name_f, __)) }; \ 153 | return (TypeclassName){ .tc = &tc, .self = x }; \ 154 | } 155 | ``` 156 | **NOTE**: The ellipsis (`...`) here does not denote variable arguments - it simply is a placeholder for more arguments. In real implementations, this should be replaced with the exact arguments of the expected functions. One wrapper function must be defined for every typeclass function, as dicussed in the [core-idea](#core-idea) section. 157 | 158 | This is very similar to the `T_to_TypeclassName_inst` above. But with a touch more "features". 159 | ```c 160 | ReturnType (*const func_name_)(T* self, ...) = (func_name_f); 161 | (void)func_name_; 162 | ``` 163 | These 2 lines are a no-op, but they are very important to the goal of this pattern- *Type safety*. The implementation functions should take in **the exact concrete type**. Implementations are inherently "user targeted" - the user **should not** be using errant void pointers. 164 | 165 | The first line ensures the function implementation is the exact type it needs to be, with `void* self` substituted for `T* self`. If you accidentally provided a function implementation with incorrect type, you'll know it. 166 | 167 | The second line silences the "unused variable" warning and allows the 2 lines to be a no-op. 168 | 169 | Otherwise, the macro is an exact analog of `T_to_TypeclassName_inst`, it takes the concrete type the implementation is for, the name to define this wrapper function as, and all the required function implementations. 170 | 171 | You can now simplify the implementation for `T` above- 172 | ```c 173 | impl_TypeclassName(T, T_to_TypeclassName_inst, T_func_name) 174 | ``` 175 | 176 | **The code snippets above are all psuedocode for a general idea. This concept is used to define a [`Show`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Text-Show.html#t:Show) typeclass for an `enum` in [barebones-macro.c](./barebones-macro.c).** 177 | 178 | # With more constraints 179 | **NOTE**: The ideas and abstractions described in this section **are not** *integral* enough to the actual pattern. If you want maximum transparency, you may safely ignore this. 180 | 181 | *Reference code: [name-constrained.c](./name-constrained.c)* 182 | 183 | Constraints are a core part of abstractions. With more constraints, you can have *more predictability* - allowing for more "syntax sugar" macros. 184 | 185 | If the user is disallowed from choosing their own names for the implementation functions - you get *predictability*. Which allows you to make a macro to wrap a user given type into the necessary typeclass. You'll need to abstract out the impl function naming in the `impl_` macro to have consistent naming- 186 | ```c 187 | #define CONCAT_(x, y) x ## y 188 | #define CONCAT(x, y) CONCAT_(x, y) 189 | 190 | /* Consistently name the impl functions */ 191 | #define ImplName(T, TypeclassName) CONCAT(CONCAT(T, _to_), CONCAT(TypeclassName, _inst)) 192 | 193 | #define impl_TypeclassName(T, func_name_f) \ 194 | ReturnType CONCAT(func_name_f, __)(void* self, ...) \ 195 | { \ 196 | ReturnType (*const func_name_)(T* self) = (func_name_f); \ 197 | (void)func_name_; \ 198 | return func_name_f(self, ...); \ 199 | } \ 200 | TypeclassName ImplName(T, TypeclassName)(T* x) \ 201 | { \ 202 | static TypeclassName_vtable const tc = { .func_name = (CONCAT(func_name_f, __)) }; \ 203 | return (TypeclassName){ .tc = &tc, .self = x }; \ 204 | } 205 | ``` 206 | 207 | Now, if you implemented `TypeclassName` for your type `T`, the function used to turn `T*` into `TypeclassName` would just be `ImplName(T, TypeclassName)`. With this knowledge, you can abstract out the "wrapping `T` into `TypeclassName`" part- 208 | ```c 209 | /* "Apply" a typeclass over a concrete type */ 210 | #define ap(x, T, TypeclassName) ImplName(T, TypeclassName)(x) 211 | ``` 212 | 213 | You can now simplify the implementation for `T` *as well as the wrapping*- 214 | ```c 215 | impl_TypeclassName(T, T_func_name) 216 | 217 | int main(void) 218 | { 219 | /* Initiate the concrete value */ 220 | T val = ...; 221 | TypeclassName x = ap(&val, T, TypeclassName); 222 | } 223 | ``` 224 | 225 | **The code snippets above are all psuedocode for a general idea. This concept is used to define a [`Show`](https://hackage.haskell.org/package/base-4.15.0.0/docs/Text-Show.html#t:Show) typeclass for an `enum` in [name-constrained.c](./name-constrained.c).** 226 | 227 | # Here be meta-macros 228 | **NOTE**: The ideas and abstractions described in this section **are not** *integral* enough to the actual pattern. If you want maximum transparency, you may safely stop ignore this. 229 | 230 | **NOTE**: If you're interested in meta macros fully implemented alongside a very similar pattern - you should check out [interface99](https://github.com/Hirrolot/interface99). Hirrolot does metaprogramming better than I can even imagine! 231 | 232 | Ah, meta programming with macros. Remarkable projects like [metalang99](https://github.com/Hirrolot/metalang99), [C99-Lambda](https://github.com/Leushenko/C99-Lambda), [obj.h](https://github.com/small-c/obj.h), [clofn.h](https://github.com/yulon/clofn) and [many more](https://github.com/Hirrolot/awesome-c-preprocessor), really showcase how ridiculously strong (and evil) the C preprocessor can be in the right hands. Surely, meta macros can be used here too? To make some magical abstractions? 233 | 234 | Well yes, not entirely sure how magical they would be though. I'm not going to go through the implementations of these - I'd rather just discuss the concepts. Following is a list of "abstractions", wrapping around the typeclass pattern, that you can implement with macros. 235 | 236 | ## Default implementations 237 | In many cases, typeclasses or interfaces have certain functions that aren't required to be implemented - and instead some default implementation is used. You can do this by having a variadic argument (`...`)[1] on the `impl_` macro, representing the optional function implementations. You'll then need macros to *work* with these variadic macros, and figure out which optional functions were provided - and which weren't. This isn't a new concept and is already used in many of the meta macro projects mentioned above. You can also find an implementation of this on [Stack Overflow](https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros). 238 | 239 | For implementations that were provided, you simply use them - as long as they pass typecheck. For ones that weren't provided, you use some default implementation you have defined already. 240 | 241 | [1] In standard C, A variadic argument represents *1 or more* arguments. Not *0 or more*. This means that you may have to have a dummy argument to really achieve the "0 or more" arguments concept that you'll need for truly optional arguments. 242 | 243 | ## Less redundancy for the definer 244 | In general, defining all the typeclasses and its respective `impl_` macro is very similar. By spamming enough meta macros, you should be able to abstract out the defining part completely. 245 | 246 | In general, you could have a singular macro to define the vtable and the typeclass instance together - it just needs to know some information about the functions. Next, you need a general `impl` macro. It should be able to deduce information about the functions of a typeclass (possibly through an object like macro), and define a function similar to how the current `impl_` macro does. You'll definitely need [`mapping`](https://github.com/swansontec/map-macro/blob/master/map.h) for this. 247 | 248 | # Limitations 249 | 1. Polymorphic return types, i.e when the return type is a typeclass instance, generally involve heap allocation. This is because the `self` member is of type- `void*`. You can only assign pointers to it. But you can't assign the address of a local variable since its lifetime ends after the function returns. 250 | 2. There's no way to have a function's **return type**, be the *exact same* as a **polymorphic input (argument) type**. This is because there's no way to know *the exact type* wrapped inside a typeclass. You can return the same polymorphic type. But in many cases, this isn't what you'd want. 251 | 252 | Consider addition- `(+) :: Num a => a -> a -> a` - 2 arguments and a return value, all of the same type. As long as the type implements `Num`. If you use `(+)` with `int`s, the return value is an `int`, with `float`s, the return value is a `float`. 253 | 254 | You simply can't do this in C, since there's no way to capture those types. You could return the `Num` typeclass instance itself. But the only thing you can (safely) do with that return value, is more `Num` operations. 255 | 256 | 3. As an extension to the point 2, [Return type polymorphism](https://eli.thegreenplace.net/2018/return-type-polymorphism-in-haskell/) (not to be confused with *polymorphic return types*) is simply not possible (safely). Which means functions like `Enum a => toEnum :: Int -> a` cannot be implemented. 257 | 4. It requires extra effort to pass [**combined typeclass instances**](#combining-multiple-typeclassesinterfaces) to functions expecting less typeclass implementations. 258 | 259 | Suppose you have the combined typeclass instance `Foo`. It contains the usual `self` member, and vtables for 3 other typeclasses `Atc`, `Btc`, `Ctc`. You want to use this with a function that just wants a type implementing `Atc`. You need to manually extract the `Atc` vtable from `Foo`, the `self` member, and then create soley the `Atc` typeclass instance to be able to use it with the aforementioned function. 260 | 5. Type safety, on functions *taking multiple typeclass instance arguments*, but **requiring** those arguments to be backed up by **the same concrete type**, cannot be guaranteed. 261 | 262 | Consider the compare function- `Ord a => compare :: a -> a -> Ordering` (assume `Ordering` is `typedef enum { LT = -1, EQ = 0, GT = 1 } Ordering;`) - 2 polymorphic arguments, *bounded by the `Ord` typeclass*, but **required** to be *the same concrete type*. It wouldn't make sense to compare an `int` with a `char*` - and yet both can be wrapped inside a `Ord` typeclass instance, as long as they implement it. So if you have 2 `Ord` typeclass instances, and you want to call `compare` from one of them, pass in `self` from both `Ord` instances - there's no guarantee that both instances are actually wrapping the same type. 263 | 264 | There's a solution to this though, but you must do it manually. Before calling `compare`, **verify equality** of the `tc` members of both `Ord` instances. If they're wrapping the same type - obtained from their respective implementation function - the typeclass address is the exact same. 265 | 266 | # Motivation 267 | It's pretty common for people to ask for polymorphism after they've written enough C. Thankfully, there's no shortage of demonstrations, helper headers, and crazy cool meta macros for implementing OOP polymorphism in C. But I was looking for an action oriented pattern with as much type safety as possible. 268 | 269 | Specifically, I wanted something like **Haskell typeclasses**, or **Java/C# interfaces**, or **Rust/Scala traits**. A sort of "interface" that declares a bunch of functions with specific types, leaving in a polymorphic `self` or the like. The exact implementations, for which, a concrete type must fill. This allows you to make polymorphic actions that ask for a type implementing a certain "interface". 270 | 271 | The end result, after about a week of experimenting and refining, is this pattern. Over many refinements and re-evaluations, I think the final result is an actually extensible and practical polymorphism pattern based around actions. It's not a perfect encoding of actual typeclasses (especially not the haskell ones - those are extremely high level). But it's probably as good as it gets. 272 | --------------------------------------------------------------------------------