{"id":13563224,"url":"https://github.com/faiface/generics","last_synced_at":"2025-04-15T19:30:23.420Z","repository":{"id":57484000,"uuid":"193960225","full_name":"faiface/generics","owner":"faiface","description":"A proof-of-concept implementation of my generics proposal for Go","archived":false,"fork":false,"pushed_at":"2019-06-30T14:34:40.000Z","size":477,"stargazers_count":97,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-29T01:12:14.906Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faiface.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-26T18:46:02.000Z","updated_at":"2025-02-23T16:54:58.000Z","dependencies_parsed_at":"2022-08-28T17:02:28.020Z","dependency_job_id":null,"html_url":"https://github.com/faiface/generics","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faiface%2Fgenerics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faiface%2Fgenerics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faiface%2Fgenerics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faiface%2Fgenerics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faiface","download_url":"https://codeload.github.com/faiface/generics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249138503,"owners_count":21218898,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T13:01:16.598Z","updated_at":"2025-04-15T19:30:22.983Z","avatar_url":"https://github.com/faiface.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# A proof-of-concept implementation of my generics proposal for Go\n\nThis program translates a Go file that uses generics into a regular Go file that can be run.\n\n```\n$ go get github.com/faiface/generics\n```\n\nThen navigate to the repo folder and run:\n\n```\n$ go install\n```\n\nThis will install the `generics` command and you should be able to use it just by typing its name (if you have your [`$PATH` set up correctly](https://golang.org/doc/code.html)).\n\nI have taken measures to prevent you from running this in production. Please, do **not** run this in production. The single measure taken is that this program only translates a single file. This means that generic functions and types are only usable within that one file.\n\nHere's a trivial example.\n\n```go\n// reverse.go\n\npackage main\n\nimport \"fmt\"\n\nfunc Reverse(a []type T) {\n    for i, j := 0, len(a)-1; i \u003c j; i, j = i+1, j-1 {\n        a[i], a[j] = a[j], a[i]\n    }\n}\n\nfunc main() {\n    a := []int{1, 2, 3, 4, 5}\n    b := []string{\"A\", \"B\", \"C\"}\n    Reverse(a)\n    Reverse(b)\n    fmt.Println(a)\n    fmt.Println(b)\n}\n```\n\nHere we have a file called `reverse.go` that uses generics. Here's how we translate it:\n\n```\n$ generics -out out.go reverse.go\n```\n\nAnd here's what we get!\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc Reverse_int(a []int) {\n    for i, j := 0, len(a)-1; i \u003c j; i, j = i+1, j-1 {\n        a[i], a[j] = a[j], a[i]\n    }\n}\n\nfunc Reverse_string(a []string) {\n    for i, j := 0, len(a)-1; i \u003c j; i, j = i+1, j-1 {\n        a[i], a[j] = a[j], a[i]\n    }\n}\n\nfunc main() {\n    a := []int{1, 2, 3, 4, 5}\n    b := []string{\"A\", \"B\", \"C\"}\n    Reverse_int(a)\n    Reverse_string(b)\n    fmt.Println(a)\n    fmt.Println(b)\n}\n```\n\nThen, of course, we can run `out.go`:\n\n```\n$ go run out.go\n[5 4 3 2 1]\n[C B A]\n```\n\n## More example\n\nThat was just a silly little example. For more complex examples, take a look into the [`examples`](examples/) directory:\n- [Slice utilities](examples/sliceutils)\n- [Channel utilities](examples/chanutils)\n- [Math utilities](examples/mathutils)\n- [Linked list](examples/list)\n- [Sync map](examples/syncmap)\n- [Priority queue](examples/priorityqueue)\n\n## The proposal\n\nThis is a refined version of a proposal I submitted a few weeks ago. [You can find the original version here.](https://gist.github.com/faiface/e5f035f46e88e96231c670abf8cab63f)\n\nThis version is very similar to the original proposal, it only differs in three things:\n1. The `gen` keyword has been replaced with two keywords: `type` and `const`. This implementation only implements the `type` keyword, `const` will be described below nonetheless.\n2. An `ord` type restriction in addition to the previously described `eq` and `num`.\n3. The `type` keyword now must also appear in the declarations of generic types.\n\nNow I will describe the proposal as concisely as I can. If you have questions, scroll down to the [FAQ](#FAQ) section.\n\n### The `type` keyword\n\nLet's start with generic functions. Here's a **pseudocode** of a generic `Map` function on slices:\n\n```go\n// PSEUDOCODE!!\nfunc Map(a []T, f func(T) U) []U {\n    result := make([]U, len(a))\n    for i := range a {\n        result[i] = f(a[i])\n    }\n    return result\n}\n```\n\n\u003e In case you don't know, a `Map` function takes a slice and a function and returns a new slice with each element replaced by the result of the function applied to the original element.\n\u003e\n\u003e For example: `Map([]float64{1, 4, 9, 16}, math.Sqrt)` returns a new slice `[]float64{1, 2, 3, 4}`, taking the square root of each of the original numbers.\n\nTo make this a **valid** Go code under my proposal, all you need to do is to mark the _first_ (and only the first) occurrence of each type parameter (= an unknown type) in the signature with the `type` keyword. The `Map` function has two:\n\n```go\n//           here              here\n//            \\/                \\/\nfunc Map(a []type T, f func(T) type U) []U {\n    result := make([]U, len(a))\n    for i := range a {\n        result[i] = f(a[i])\n    }\n    return result\n}\n```\n\nNothing else changed.\n\nThe `type` keyword basically declares a type parameter in a signature. The name is then visible in the entire scope of the function.\n\nThere are three rules about the placement of the `type` keyword in signatures:\n1. It's only allowed in package-level function declarations.\n2. In functions, it's only allowed in the list of parameters. Particularly, it's **disallowed** in the list of results.\n3. In methods, it's only allowed in the receiver type.\n\nThe last two rules can be remembered together: `type` is only allowed inside the first pair of parentheses.\n\n### Unnamed type parameters\n\nOkay, so no `type` in the list of results. But how do we make a function like this `Read`? The only occurrence of the `T` type is in the result:\n\n```go\n// DISALLOWED!!\nfunc Read() type T {\n    var x T\n    fmt.Scan(\u0026x)\n    return x\n}\n```\n\nTo make this function work, we need to use an _unnamed type parameter_. It's basically a dummy generic parameter:\n\n```go\nfunc Read(type T) T {\n    var x T\n    fmt.Scan(\u0026x)\n    return x\n}\n```\n\nThe value of the unnamed parameter is irrelevant. We're only interested in the type. That's why when calling the `Read` function, we pass in the type directly:\n\n```go\nfunc main() {\n    name := Read(string)\n    age := Read(int)\n    fmt.Printf(\"%s is %d years old.\", name, age)\n}\n```\n\nDon't worry, this doesn't send us to the [dependent typing land](https://en.wikipedia.org/wiki/Dependent_type) because we can't return types, only accept them.\n\nIt's simple, if a parameter is an unnamed generic parameter, you pass a type directly. Otherwise, you pass a value and the type system will infer the type.\n\nThis notation also makes it possible to give a type to the built-in `new` function:\n\n```go\nfunc new(type T) *T {\n    var x T\n    return \u0026x\n}\n```\n\n**One important rule:** generic functions cannot be used as values. They can't be assigned to variables and they can't be passed as arguments. To pass a specialized version of a generic function as an argument, wrap it in an anonymous function, like this:\n\n```go\nSomeFunction(func() int {\n    return Read(int)\n})\n```\n\n### Restricting types\n\nSome functions (or types) want to declare that they don't work with all types, but only with ones that satisfy some conditions. For example, the keys of a map must be comparable. That is a restriction. A `Min` function only works on types that are orderable (i.e. can be compared with `\u003c`).\n\nInitially, my proposal excluded any support for restricting types for the purpose of simplicity. The [contracts proposal](https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md) by the Go team has received (justifiable) criticism for introducing complexity by supporting _contracts_, which make it possible to specify arbitrary restrictions on types.\n\nBut some restrictions are extremely useful. That's why I eventually decided to include three possible restrictions that should cover the majority of use-cases. This decision is governed by the [80/20 principle](https://en.wikipedia.org/wiki/Pareto_principle).\n\nHere are the three possible restrictions:\n1. **`eq`** - Comparable with `==` and `!=`. Usable as map keys.\n2. **`ord`** - Comparable with `\u003c`, `\u003e`, `\u003c=`, `\u003e=`, `==`, `!=`. A subset of `eq`.\n3. **`num`** - All numeric types: `int*`, `uint*`, `float*`, and `complex*`. Operators `+`, `-`, `*`, `/`, `==`, `!=`, and converting from untyped integer constants works. Not a subset of `ord`.\n\nTo use a type restriction, place it right after the first occurrence of the type parameter.\n\nFor example, here's the generic `Min` function:\n\n```go\n//                   here\n//                    v\nfunc Min(x, y type T ord) T {\n    if x \u003c y {\n        return x\n    }\n    return y\n}\n```\n\nNotice that `num` is not a subset of `ord`. This is because the complex number types are not comparable with `\u003c`. To accept only the numeric types that are also orderable, combine the two restrictions like this: `type T ord num`.\n\nThe `eq`, `ord`, and `num` words have no special meaning outside of the generic definitions. They are not keywords.\n\n### Generic types\n\nWe've covered everything about generic functions, let's move on to generic types.\n\nTo define a generic type, simply list the type parameters in parentheses right after the type name. Like this:\n\n```go\n// List is a generic singly-linked list.\ntype List(type T) struct {\n    First T\n    Rest  *List(T)\n}\n```\n\nAnd as you can already see in the definition, to use a generic type, list the arguments in parentheses after the type name. For example `List(int)` is a list of integers, and `List(string)` is a list of strings.\n\nWhen defining a type with multiple generic parameters, mark each one with `type`:\n\n```go\n// SyncMap is a generic hash-map usable from multiple goroutines simultaneously.\ntype SyncMap(type K eq, type V) struct {\n    mu sync.Mutex\n    m  map[K]V\n}\n```\n\nMethods work as usual:\n\n```go\nfunc (sm *SyncMap(type K eq, type V)) Store(key K, value V) {\n    sm.mu.Lock()\n    sm.m[key] = value\n    sm.mu.Unlock()\n}\n```\n\nBut don't forget that the `type` keyword is only allowed in the receiver type. For explanation, see [FAQ](#FAQ).\n\n### Generic array lengths (unimplemented)\n\nThe original proposal also included generic array lengths. There is still an intention to support them, but I haven't implemented them yet, because this has been enough work so far. They'd work like this:\n\n```go\nfunc Reverse(a *[const n]type T) {\n    for i, j := 0, n-1; i \u003c j; i, j = i+1, j-1 {\n        a[i], a[j] = a[j], a[i]\n    }\n}\n```\n\nAnd that's all! Happy hacking!\n\n## FAQ\n\n### Is this an officially accepted proposal?\n\nNo! Enjoy it, experiment with it, and don't complain about the syntax ;). Eh, you can, but you know, don't overdo it.\n\n### Does this work?\n\nYep! There's only one limitation: it only translates a single file. And there's only one unimplemented feature: generic array lengths.\n\n### What are the advantages of this syntax?\n\nMost proposals propose a syntax that introduces another pair of parentheses in function declarations, like this:\n\n```go\nfunc Map(type T, U)(a []T, f func(T) U) []U {\n    // ...\n}\n```\n\nThere are four main advantages of my syntax compared to the other proposals:\n1. **It's clear where a type parameter gets inferred.** In my proposal concrete type of a type parameter gets inferred exactly where the `type` keyword is. With other proposals, it's not clear where it gets inferred and if it can be inferred at all.\n2. **It's clear whether a type parameter must be specified manually by the caller.** In my proposal, if a type parameter is unnamed, it must be specified by the caller manually. Otherwise, it gets inferred from an argument. There is never a choice between specifying and inferring. In other proposals, it's not clear when the caller must specify the types manually and when they can be inferred, because it depends on the power of the type-checker.\n3. **Fits in with built-in Go functions like `make` and `new`.** The unnamed type parameters even make it possible to give a type to the built-in `new` function. The `make` function is a little more [funky](https://faiface.github.io/funky-tour/), though. It would also require function overloading.\n4. **No extra parentheses.** Better readability.\n\nFurthermore, it introduces no new keywords.\n\n### What are some other advantages of this proposal?\n\nThis is to be argued, but here's what I think:\n1. **Easy to understand and read.** Doesn't introduce complexity.\n2. **Orthogonal to other Go features.** For example, these generics don't collide with interfaces. Every problem is either suitable for generics, or for interfaces, (or not none of them), but rarely suitable for both.\n3. **Fast and straightforward type-checking.** The type-checking is super simple. Just substitute a concrete type for a type parameter where it says `type` in the signature and you're good to go.\n\n### Why no `type` keyword in function results?\n\nBecause it would make type-checking ambiguous. Let's say that `Read` can be written like this:\n\n```go\nfunc Read() type T {\n    var x T\n    fmt.Scan(\u0026x)\n    return x\n}\n```\n\nNow, what types should it read here?\n\n```go\nname := Read()\nage := Read()\nfmt.Printf(\"%s is %d years old.\", name, age)\n```\n\nShould it infer based on the `%s` and `%d` placeholders in `fmt.Printf`? I don't think so.\n\nForbidding the use of `type` in results forces the programmer to use unnamed type parameters when needed. They, in turn, make it possible to specify the type parameters manually for the caller.\n\n### Why is the `type` keyword only allowed in the receiver in methods?\n\nThere two reasons for this.\n1. **Methods are used to satisfy interfaces.** Methods that are generic beyond their receiver would seriously complicate this prospect. Either the interfaces would have to require generic methods, or the type-checker would have to be able to specialize generic methods for the purpose of satisfying interfaces. Both would complicate the system.\n2. **Reflection.** Reflection makes it possible to discover all methods of a type at runtime. If methods generic beyond their receiver would be possible, reflection would need to be able to discover generic methods. This could be possible but would be quite complex. This is the same reason that generic functions aren't usable as values.\n\n### Why no ability to create my own restrictions?\n\nBecause that's where all the unwanted complexity comes from.\n\nJust take a look at Haskell. [Type clasess](https://en.wikipedia.org/wiki/Type_class) in Haskell are a way to specify your own restrictions. They are even simpler than the contracts proposed by the Go team. Yet, you get `Functor`, `Applicative`, `Monad`, `Monoid`, `Traversal`, and a whole bunch of abstract functions that don't make any sense unless you've spent two years studying them. And that's not all. There's a whole culture that makes you spend more time implementing various type classes than implementing the actually useful code.\n\nOf course, I'm exaggerating, but just a little bit. Haskell is a great language, but complex. Also, Go would not become Haskell. But there would be the tools and people would misuse them somehow.\n\nFurthermore, most situations for these custom restrictions are already covered by interfaces. With generics, interfaces become even stronger.\n\n### How did you do this?\n\nI copied the whole tree of [`\"go/*\"`](https://golang.org/pkg/go/) packages from the standard library. They implement parsing, importing, and type-checking of Go code. Then I extended them (namely `\"go/ast\"`, `\"go/parser\"`, `\"go/printer\"`, `\"go/types\"`) with support for generics. Parsing generics, type-checking generics. I also made them emit special information about generic calls and type instances that made it easier to implement the translating tool.\n\nNow, the translation itself it a bit hacky. It works in passes. A single pass works like this:\n1. Parse and type-check the code.\n2. Find all non-generic functions.\n3. In them, find all generic calls and generic type instances (their type parameters must be concrete).\n4. Instantiate the found generic functions and types with the used parameters. This means copy-pasting their original generic implementation and replacing all uses of the type parameters with concrete types.\n5. Replace all generic calls and type instances in the non-generic function with calls to the new, instantiated functions and types.\n6. Write the result.\n\nAnd I repeat this process until nothing changes. In the end, I remove all generic functions from the source and write the final result.\n\n### Can I break it?\n\nSure. There are bugs, 100%. I've already caught and fixed many of them, but if you find some new, please file an issue.\n\n### Why is the generated code so ugly?\n\nSorry. Blame `\"go/printer\"`.\n\n### Why no tests?\n\nThis is the test.\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaiface%2Fgenerics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaiface%2Fgenerics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaiface%2Fgenerics/lists"}