The gist https://gist.github.com/anonymous/68c3b072538ec48c2667f7db276e781b is a minimal simplified example of a repeated golang code pattern that I have encountered in an existing code base which I am trying to document. From its usage it appears to be similar to a mixin, but neither myself or any of my colleagues have actually seen this pattern before in golang. Can anyone tell me an appropriate established name for this pattern?
The gist attempts to illustrate:
It does not seem to strictly satisfy much of the definitions for a mixin as it manipulates the host object in ways that were beyond M's knowledge when it was written (potentially modifying host state) and doesn't directly add methods to the host type:
And it doesn't seem to follow the same pattern as some self proclaimed 'golang mixins' I have found:
A mixin is basically a piece of code to be included ('mixed in') to another:
// WARNING: Messy, untested C code ahead
// Illustrates mixins in a non-OO language.
// mixin_hasname.h
char *name;
// mixin_hasname_impl.h
struct hasname_vtable { // Vtable for storing mixin functions. Saves a lot of memory in the long run, as only one vtable has to be defined per type.
char *(*getname)(void *self);
};
#define CAT(X,Y) X##Y
#define CATX(X,Y) CAT(X,Y)
#define GEN_HASNAME_VTABLE(TYPENAME,PRENAME)\
static struct hasname_vtable {\
.getname = CATX(PRENAME,getname)\
} CATX(PRENAME,vtable);\
static char *CATX(PRENAME,getname)(void *self) {\
return ((TYPENAME *)self)->name;\
}
// main.c
struct my_c_struct {
// include our mixin fields
#include <mixin_hasname.h>
int x, y, z;
};
// include our mixin implementation
#include <mixin_hasname_impl.h>
// generate an implementation for our struct
GEN_HASNAME_VTABLE(struct my_c_struct, mycstruct_)
int indirect(struct my_c_struct *x, struct hasname_vtable *hasname_vt) {
printf("%s
", hasname_vt.getname(x));
}
int main(void) {
struct my_c_struct x;
x.name = "Name";
x.x = 0, x.y = 0, x.z = 0;
printf("%s
", mycstruct_getname(&x)); // Notice we never had to define mycstruct_getname ourselves; we avoided code duplication
indirect(&x, mycstruct_vtable); // Generally, vtables are passed. Some languages pass typeid's or function pointers, however. (Few put their vtable in their data type (in this case `x`) because of the inefficient memory usage. In the case of C, though, it saves you from having to pass your vtable everytime you call an indirect function).
return 0;
}
This also holds true for a mixin in OOP, but OOP adds a lot of restrictions on mixins. For example, mixins might not have access to private variables, child class members, class methods, etc. Golang is not really OO.
So, with that out of the way, is this an example of a mixin? No, I wouldn't say so. The biggest problem I'm having with answering that question isn't that your pattern doesn't follow the definition defined for OO (Because Golang is not OO anyway), but that it doesn't really 'mix in' the code, it's just storing it in it's own field.
struct struct_with_name_field {
char *name;
}
// main.c
struct my_c_struct {
struct struct_with_name_field namefield;
int x, y, z;
}
int main(void) {
struct my_c_struct x;
x.namefield.name = "Name";
x.x = 0, x.y = 0, x.z = 0;
return 0;
}
However, I do think it's perfectly reasonable to call this pattern a mixin within your team or your documentation, especially because, as far as I know, real mixins aren't possible in Golang anyway, and this pattern is as close as you will get.
Golang doesn't allow you to define mixins (only interfaces), so your pattern isn't a mixin. However, given the definition of mixin (Especially outside OOP), and the fact that your pattern is probably the closest you will get to mixins in Golang, it is very reasonable to call this a mixin anyway.
Your example is just composition with exposed struct fields. While this is similar to mixins, it is not the same. You even gave examples for real mixins (in Go).
I'll give one myself and tell why that can be useful.
Let's say you want to yell at people. Then you need a Yeller
interface. Also, in this case, we want a Yell
function which takes a Yeller
and uses it to yell. Here is the code:
type Yeller interface {
Yell(message string)
}
func Yell(m Yeller, message string) {
m.Yell(message)
}
Who can yell? Well, people yell, so we will create one. We could of course implement the Yeller
interface on the person directly:
type Person struct {}
func (p *Person) Yell(message string) { /* yell */ }
// Let a person yell.
person := &Person{}
Yell(person, "No")
Now Person
is stuck to an implementation and maybe we don't want that. So, you already gave a solution:
type Person struct {
Yeller Yeller
}
person := &Person{ /* Add some yeller to the mix here */ }
But now, if we want to let person
yell, we cannot use our function directly, because Person
does not implement Yeller
.
// Won't work
Yell(person, "Loud")
Instead, we have to explicitly tell Yell
to use the Person
's Yeller
.
// Will work
Yell(person.Yeller, "No")
There is another possibility. We could let Person
implement Yeller
by passing the call to the Person
's Yeller
.
func (p *Person) Yell(message string) {
p.Yeller.Yell(message)
}
// Will work again!
Yell(person, "Yes")
But this forces us to write a lot of boilerplate code, the method count of our mixin times the number of "implementations".
We can do better by using Go's mixin facilities.
type Person struct {
Yeller
}
p := &Person { /* Add some Yeller to the mix here */ }
Yell(p, "Hooray")
Now Person
is a Yeller
, passing the Yell
call to the wrapped Yeller
. No boilerplate needed.
Why is that useful? Imagine you also want to Whisper
, creating a Whisperer
interface, and you want a RoboticVoice
which also can Whisper
and Yell
.
You would write analogous code and both Person
and RoboticVoice
could both be composed taking different implementations of Yeller
s and Whisperer
s.
Last but not least, you are still able to overwrite behaviour by letting the structs implementing the methods themselves.
Here is the full example code and a link to the Golang playground:
package main
import (
"fmt"
)
func main() {
Yell(&Person{ Yeller: yeller("%s!!!
") }, "Nooooo")
Yell(&RoboticVoice{Yeller: twiceYeller("*** %s ***")}, "Oh no")
Whisper(&Person{ Whisperer: whisperer("Sssssh! %s!
")}, "...")
Whisper(&RoboticVoice{ Whisperer: whisperer("Sssssh! %s!
")}, "...")
}
type Yeller interface {
Yell(message string)
}
func Yell(y Yeller, message string) {
y.Yell(message)
}
type Whisperer interface {
Whisper(message string)
}
func Whisper(w Whisperer, message string) {
w.Whisper(message)
}
type Person struct {
Yeller
Whisperer
}
type RoboticVoice struct {
Yeller
Whisperer
}
func (voice *RoboticVoice) Yell(message string) {
fmt.Printf("BEEP! ")
voice.Yeller.Yell(message)
fmt.Printf(" BOP!
")
}
func (voice *RoboticVoice) Whisper(message string) {
fmt.Printf("Error! Cannot whisper! %s
", message)
}
type yeller string
func (y yeller) Yell(message string) {
fmt.Printf(string(y), message)
}
type twiceYeller string
func (twice twiceYeller) Yell(message string) {
fmt.Printf(string(twice+twice), message, message)
}
type whisperer string
func (w whisperer) Whisper(message string) {
fmt.Printf(string(w), message)
}