3

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:

  • Unmodified behaviour implementation code (M's funcs) used by multiple types (A and B).
  • Included by composition into a host type (A and B).
  • Behaviour methods exported and accessible via a host.behaviour.method(...) call e.g. a.Formatter.FormatText(...).
  • Host methods are acted upon by the behaviour.
  • The behaviour holds state (private or exported) which modifies the execution of the behaviour.
  • The type that has add a specific behaviour added can be used by a function needing that behaviour by passing the behaviour field e.g. b := NewB().Formatter.
  • Many different behaviours can be composed into a given type (not actually shown for brevity, but you could imagine an M1, M2, M3, etc being included into A or B).

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:

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
Richard
  • 136
  • 1
  • 12

2 Answers2

16

TLDR

  • These are not mixins.
  • Yes, Go has mixins.

Longer

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 Yellers and Whisperers.

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!!!\n") }, "Nooooo")
    Yell(&RoboticVoice{Yeller: twiceYeller("*** %s ***")}, "Oh no")
    Whisper(&Person{ Whisperer: whisperer("Sssssh! %s!\n")}, "...")
    Whisper(&RoboticVoice{ Whisperer: whisperer("Sssssh! %s!\n")}, "...")
}

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!\n")
}

func (voice *RoboticVoice) Whisper(message string) {
    fmt.Printf("Error! Cannot whisper! %s\n", 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)
}

Playground

GodsBoss
  • 1,019
  • 8
  • 9
  • That is quite interesting; I have always considered and referred to your example as a vanilla use of interfaces or composition respectively. Your answer is useful to keep it clear what is being talked about. – Richard Jun 26 '17 at 09:30
  • Note that the question can be summarised as 'what pattern is this code' rather than how to write a mixin / advisable method of writing code. – Richard Jun 26 '17 at 09:31
  • This is a wonderful answer. Mixins are just orthogonal pieces of implementation that can be mixed and matched. By that logic, everything which is composed is, in essence, a mixin; composition allows borrowing implementation from the composed object, just like a mixin. However, the boilerplate code necessary to support function forwarding through composed objects is troublesome, so we typically distinguish traditional composition from the term "mixin", even though they're essentially the same concept. Embedded structs just remove that boilerplate code, thus they are perfect examples of mixins. – Alexander Guyer Dec 16 '20 at 22:15
  • the line you are telling (// Won't work) actually works. you are repeating the same code just below and this time you claim it to be working. – Özgür May 16 '21 at 21:08
  • 1
    @Özgür: The "won't work" line refers to the second implementation of `Person`. – GodsBoss May 17 '21 at 08:00
  • @godsboss , here is the code snippet you claim not to work, along with your fix . They both work and give the same output https://play.golang.org/p/D2THwY6GGxv . i just typed the code you addressed. Do i still get it wrong, if so, can you give the link of the code snippet that will not work? – Özgür May 18 '21 at 07:39
  • 1
    @Özgür: I gave three examples of a person yelling, your example shows the _third_ example, not the second. I made playgrounds for all three of these, note that the second one does _not_ work: 1. https://play.golang.org/p/QkDBz1mVbd7 2. https://play.golang.org/p/PHtWLzn52yl 3. https://play.golang.org/p/JsIV-jBBYq3 – GodsBoss May 18 '21 at 09:14
  • 1
    Thanks, this makes it clear now. I couldnt see the difference between "Yeller Yeller" and just "Yeller" . @GodsBoss – Özgür May 18 '21 at 11:16
2

What is a mixin?

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\n", 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\n", 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.

Can this code pattern be called a mixin?

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.

Summary

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.

yyny
  • 1,623
  • 18
  • 20