@@ -10,22 +10,20 @@ categories:
1010 - Tutorial
1111---
1212
13- Today I'm going to quickly go over one of my favourite convenience functions,
14- made possible with Go's Generics. It's invaluable if you deal with maps of maps
15- in Go.
13+ Today, I'm going to quickly go over one of my favorite convenience functions,
14+ made possible with Go's generics. It's invaluable if you deal with maps of maps in Go.
1615
1716<!-- more-->
1817
1918### Maps
2019
21- Maps are one of the the original "generic" types available to Go users.
22- If you aren't familiar with them, you can learn about them in [ Effective Go] ( https://go.dev/doc/effective_go#maps ) .
20+ Maps are one of the original "generic" types available to Go users. If you aren't
21+ familiar with them, you can learn about them in [ Effective Go] ( https://go.dev/doc/effective_go#maps ) .
2322
2423The important bit is that the key type must be ` comparable ` , which is a special
25- built in interface, that defines all comparable types.
24+ built- in interface that defines all comparable types.
2625See https://pkg.go.dev/builtin#comparable for more details on that.
2726
28-
2927### Nested Maps
3028
3129However, the values of those maps can be any Go type at all, including any other map.
@@ -35,75 +33,73 @@ However, the values of those maps can be any Go type at all, including any other
3533var mapOfMaps map [string ]map [string ]string
3634```
3735
38- And the values for those * nested* maps, can also have maps.
36+ And the values for those nested maps can also be maps.
3937
4038``` go
41- // nested maps, two deep.
39+
40+ // nested maps, three deep.
4241var mapOfMapsOfMaps map [string ]map [string ]map [string ]string
4342```
4443
4544### Setting nested maps
4645
47- But this nesting has a downside. What if you want to get to that final map, so
48- you can set the value on it's key.
46+ But this nesting has a downside. What if you want to get to that final map so
47+ you can set the value on its key?
4948
50- Say it's a field Map on a struct, and we have a method to set the inner most value.
51- The code would then need to initialize all the maps along the way to setting the
52- value.
49+ Say it's a field Map on a struct, and we have a method to set the innermost value.
50+ The code would then need to initialize all the maps along the way to setting the value.
5351
5452``` go
55- func (w *mWrapper ) Set (k1 ,k2 ,k3 , value string ) {
56- if w.Map == nil {
57- w.Map = make (map [string ]map [string ]map [string ]string )
58- }
59- v1 , ok := w.Map [k1]
60- if !ok {
61- v1 = make (map [string ]map [string ]string )
62- w.Map [k1] = v1
63- }
64- v2 , ok := v1[k2]
65- if !ok {
66- v2 = make (map [string ]string )
67- v1[k2] = v2
68- }
69- v2[k3] = value
70- }
53+
54+ func (w *mWrapper ) Set (k1 , k2 , k3 , value string ) {
55+ if w.Map == nil {
56+ w.Map = make (map [string ]map [string ]map [string ]string )
57+ }
58+ v1 , ok := w.Map [k1]
59+ if !ok {
60+ v1 = make (map [string ]map [string ]string )
61+ w.Map [k1] = v1
62+ }
63+ v2 , ok := v1[k2]
64+ if !ok {
65+ v2 = make (map [string ]string )
66+ v1[k2] = v2
67+ }
68+ v2[k3] = value
69+ }
7170```
7271
73- What's happening here, is that for each level with an inner map, we need to
74- check if that map exists. We use the "comma ok" idiom, where the value of OK
75- indicates if the map as a value for the given key or not. If it doesn't exist,
76- we make a new instance of the map type, and assign it both to the value variable,
77- and to the key in the map itself.
72+ What's happening here is that for each level with an inner map,
73+ we need to check if that map exists. We use the "comma ok" idiom,
74+ where the value of ok indicates if the map has a value for the given key or not.
75+ If it doesn't exist, we make a new instance of the map type and assign it both
76+ to the value variable and to the key in the map itself.
7877
7978As you can imagine, the more layers of nesting you have, the more tedious
80- creating the earlier nested map types become .
79+ creating the earlier nested map types becomes .
8180
8281### Generics to the rescue!
8382
8483Using generics, we can avoid some of this!
8584
8685``` go
87- func (w *mWrapper ) Set (k1 ,k2 ,k3 , value string ) {
88- if w.Map == nil {
89- w.Map = make (map [string ]map [string ]map [string ]string )
90- }
91- v1 := getOrMake (w.Map , k1)
92- v2 := getOrMake (v1, k2)
93- v2[k3] = value
94- }
86+ func (w *mWrapper ) Set (k1 , k2 , k3 , value string ) {
87+ if w.Map == nil {
88+ w.Map = make (map [string ]map [string ]map [string ]string )
89+ }
90+ v1 := getOrMake (w.Map , k1)
91+ v2 := getOrMake (v1, k2)
92+ v2[k3] = value
93+ }
9594```
9695
97- Now it's eight lines shorter! 4 per level we've removed, including the tedious
98- repetition of the nested type.
96+ Now it's eight lines shorter! Four per level we've removed, including the
97+ tedious repetition of the nested type.
9998
100- Here's the code for ` getOrMake ` .
99+ Here's the code for getOrMake.
101100
102101``` go
103-
104- func
105-
106- // getOrMake is a generic helper function for extracting or initializing a sub map.
102+ // getOrMake is a generic helper function for extracting or initializing a sub-map.
107103func getOrMake[K, VK comparable, VV any, V map [VK]VV, M map [K]V](m M , key K ) V {
108104 v , ok := m[key]
109105 if !ok {
@@ -114,61 +110,60 @@ func getOrMake[K, VK comparable, VV any, V map[VK]VV, M map[K]V](m M, key K) V {
114110}
115111```
116112
117- First we've declared our generic types.
118-
119- * K is the key for the outermost map.
120- * VK is the key for the value map.
121- * VV is the value for the value map,
122- * V is the type of the value map: ` map[VK]VV `
123- * M the type of the map being passed in, ` map[K]V `
113+ First, we've declared our generic types:
124114
125- The function then takes in an instance ` m ` of type ` M ` , and the ` key ` of type ` K ` ,
126- returning an instance of the the value map.
115+ K is the key for the outermost map.
116+ VK is the key for the value map.
117+ VV is the value for the value map.
118+ V is the type of the value map: map[ VK] VV
119+ M is the type of the map being passed in: map[ K] V
120+ The function then takes in an instance m of type M and the key of type K,
121+ returning an instance of the value map.
127122
128- The then the code is pretty straightforward.
123+ Then the code is pretty straightforward.
129124
130- We lookup the value of the key, and whether it exists or not. If the key has
131- no value, then we do as we did before: create an instance of the value map, and
132- assign that to the key. Then we return the value itself.
125+ We look up the value of the key and whether it exists or not.
126+ If the key has no value, then we do as we did before:
127+ create an instance of the value map and assign that to the key.
128+ Then we return the value itself.
133129
134- I wouldn't pull out ` getOrMake ` the first time I need it, and it only works for
135- maps. But if I start repeating the pattern around in a package, or I start to
136- play with the inner types, using ` getOrMake ` can reduce some effort around that
137- refactoring.
130+ I wouldn't pull out getOrMake the first time I need it, and it only works for maps.
131+ But if I start repeating the pattern around in a package, or I start to play
132+ with the inner types, using getOrMake can reduce some effort around that refactoring.
138133
139- ### Aside: Top Level Maps
134+ ### Aside: Top- Level Maps
140135
141- It also can't work for top level maps. For example.
136+ It also can't work for top- level maps. For example:
142137
143138``` go
144139func NoArgMakeMap [K comparable, V any, M map [K]V]() M {
145- return make (M)
140+ return make (M)
146141}
147142```
148143
149- That doesn't work, since Go's type inference doesn't pull from the return value
150- even if it would be helpful.
144+ That doesn't work, since Go's type inference doesn't pull from the return value,
145+ even if it would be helpful.
151146
152- The closest you can get is to explicitly hint the type, by including an unused
147+ The closest you can get is to explicitly hint at the type by including an unused
153148parameter.
154149
155150``` go
156151func OneArgMakeMap [K comparable, V any, M map [K]V](_ M ) M {
157- return make (M)
152+ return make (M)
158153}
159154```
160155
161- That'll be enough to have the compiler do the right thing.
156+ That'll be enough to have the compiler do the right thing.
162157
163- There is at least [ one] ( https://github.com/golang/go/issues/50285 ) issue in the
164- Go issue tracker for this feature request, but no real proposals at this time.
158+ There is at least one issue in the Go issue tracker for this feature request,
159+ but no real proposals at this time.
165160
166- I don't think it's critical for Go to get this though, since the work around
161+ I don't think it's critical for Go to get this, though, since the workaround
167162isn't terrible at this point.
168163
169164### Conclusion
170165
171- Generics in Go are a powerful tool to reduce boiler plate , even with small
172- utility functions. Especially with Go's original generic types, like ` map ` !
166+ Generics in Go are a powerful tool to reduce boilerplate , even with small
167+ utility functions, especially with Go's original generic types, like map!
173168
174169Thanks for reading.
0 commit comments