Skip to content

Instantly share code, notes, and snippets.

@deanveloper
Created December 11, 2018 19:37
Show Gist options
  • Save deanveloper/0a1af8edd4a670e7c6912b284abb313f to your computer and use it in GitHub Desktop.
Save deanveloper/0a1af8edd4a670e7c6912b284abb313f to your computer and use it in GitHub Desktop.

My previous proposal was pretty well received, but @networkimprov had mentioned that I'm solving three separate problems at once, rather than addressing them separately. I think that's an important thing to consider, so hopefully this new proposal addresses this. This proposal is largely inspired by my previous proposal, so I very highly recommend reading that on to see the design philosophy. golang/go#28987

That proposal also goes over how other languages handle enums, and how we can learn from them.

Problem

The problem with non-namespaced variables is largely illustrated in my previous proposal, but to summarize, if my mat package has a Material enum and a Hardness enum in my package, I would want these things to be separated into mat.Material.Metal and mat.Hardness.Hard values, rather than just mat.Hard and mat.Metal being in the same namespace.

Solution

There are two types of namespaces - typed, and untyped. Note that "untyped" in this context does not mean the same thing as an untyped constant. In this case, "untyped" simply means that the namespace is not restricted to only defining values of one type.

An untyped namespace looks like this:

namespace Untyped {
    var (
        Foo int64 = 0
        Bar = "Bar"
    )

    var unexported = "secret"

    const Fifty = 50 // Untyped.Fifty is an untyped integer constant

    func Baz() {
        fmt.Println("Hello World!")
    }
}

And a typed namespace would look like this:

namespace Typed int {
    var (
        Foo = 1
        Bar = 100

        unexported = 6
    )

    const Fifty = 50 // open question - should this be an `int` or a compile error?
}

Referencing values is relatively simple and intuitive:

Untyped.Foo // 0 (int64)
Untyped.Foo = 5
Untyped.Foo // 5 (int64)
Untyped.Fifty // 50 (untyped integer constant)
Untyped.Fifty = 5 // compile error, Untyped.Fifty is a constant

Typed.Foo // 1 (int)
// etc...

// problem with integrating into enums

My initial idea was to allow <typed namespace> <type> to be a type, so that the following syntaxes would be valid:

// declare a type namespace together, essentially creating an enum. This is a familiar syntax
// but isn't consistent. This part of the syntax is irrelevant for now, though.
type MyEnum namespace int {
    // ...
}

// or

// internalNamespace cannot be a type on its own, as it isn't declared with `type`
namespace internalNamespace int {
    // ...
}

// now we have a type
type MyEnum internalNamespace int

// then later on

var myNsVariable internalNamespace int // inline
var myNsVariable2 MyEnum // almost the same, MyEnum is a distinct type though.

The issue is that we cannot have any mutable values in a type associated with a namespace. Take the following example:

type MyEnum namespace int {
    var X = 5
}

func main() {
    var x = 5 // ok
    MyEnum.X = 10 // uhhhhh
    
    // now x is of type MyEnum, but MyEnum does not contain a `5`.
}

We want mutable values in a namespace proposal. Ideally a namespace would be identical to package-level variable syntax in order to be consistent.

A possible solution would be allowing the const modifier on a namespace:

const namespace int {
    const (
        x = 5
        y = 10
    )
}

... But that's kind-of ugly with redeclaring const. We can't get rid of the outer const because we need to assert that this namespace won't contain any mutable values (so that enums know what values to limit a type to be able to hold), and we can't get rid of the inner const because we should ideally be matching the standard "top-level-declaration" syntax of package-level variables. It looks ugly, and doesn't feel like Go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment