diff options
author | Akshay <[email protected]> | 2020-08-01 12:07:25 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2020-08-01 12:07:25 +0100 |
commit | 6dc5eb710c27a3fee4a132763c106c5ce16f72ab (patch) | |
tree | 39c8914e039bad82f6bf5741d9b5743cdfcf460f /posts | |
parent | cc72d07ae82386e9f087517ccf2653e17c76714a (diff) |
add new post: Gripes with Go
Diffstat (limited to 'posts')
-rw-r--r-- | posts/gripes_with_go.md | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/posts/gripes_with_go.md b/posts/gripes_with_go.md new file mode 100644 index 0000000..60aabc8 --- /dev/null +++ b/posts/gripes_with_go.md | |||
@@ -0,0 +1,177 @@ | |||
1 | You've read a lot of posts about the shortcomings of the Go | ||
2 | programming language, so what's one more. | ||
3 | |||
4 | 1. [Lack of sum types](#lack-of-sum-types) | ||
5 | 2. [Type assertions](#type-assertions) | ||
6 | 3. [Date and Time](#date-and-time) | ||
7 | 4. [Statements over Expressions](#statements-over-expressions) | ||
8 | 5. [Erroring out on unused variables](#erroring-out-on-unused-variables) | ||
9 | 6. [Error handling](#error-handling) | ||
10 | |||
11 | ### Lack of Sum types | ||
12 | |||
13 | A "Sum" type is a data type that can hold one of many states | ||
14 | at a given time, similar to how a boolean can hold a true or | ||
15 | a false, not too different from an `enum` type in C. Go | ||
16 | lacks `enum` types unfortunately, and you are forced to | ||
17 | resort to crafting your own substitute. | ||
18 | |||
19 | A type to represent gender for example: | ||
20 | |||
21 | ```go | ||
22 | type Gender int | ||
23 | |||
24 | const ( | ||
25 | Male Gender = iota // assigns Male to 0 | ||
26 | Female // assigns Female to 1 | ||
27 | Other // assigns Other to 2 | ||
28 | ) | ||
29 | |||
30 | fmt.Println("My gender is ", Male) | ||
31 | // My gender is 0 | ||
32 | // Oops! We have to implement String() for Gender ... | ||
33 | |||
34 | func (g Gender) String() string { | ||
35 | switch (g) { | ||
36 | case 0: return "Male" | ||
37 | case 1: return "Female" | ||
38 | default: return "Other" | ||
39 | } | ||
40 | } | ||
41 | |||
42 | // You can accidentally do stupid stuff like: | ||
43 | gender := Male + 1 | ||
44 | ``` | ||
45 | |||
46 | The Haskell equivalent of the same: | ||
47 | |||
48 | ```haskell | ||
49 | data Gender = Male | ||
50 | | Female | ||
51 | | Other | ||
52 | deriving (Show) | ||
53 | |||
54 | print $ "My gender is " ++ (show Male) | ||
55 | ``` | ||
56 | |||
57 | ### Type Assertions | ||
58 | |||
59 | A downcast with an optional error check? What could go | ||
60 | wrong? | ||
61 | |||
62 | Type assertions in Go allow you to do: | ||
63 | |||
64 | ```go | ||
65 | var x interface{} = 7 | ||
66 | y, goodToGo := x.(int) | ||
67 | if goodToGo { | ||
68 | fmt.Println(y) | ||
69 | } | ||
70 | ``` | ||
71 | |||
72 | The error check however is optional: | ||
73 | |||
74 | ```go | ||
75 | var x interface{} = 7 | ||
76 | var y := x.(float64) | ||
77 | fmt.Println(y) | ||
78 | // results in a runtime error: | ||
79 | // panic: interface conversion: interface {} is int, not float64 | ||
80 | ``` | ||
81 | |||
82 | ### Date and Time | ||
83 | |||
84 | Anyone that has written Go previously, will probably already | ||
85 | know what I am getting at here. For the uninitiated, parsing | ||
86 | and formatting dates in Go requires a "layout". This | ||
87 | "layout" is based on magical reference date: | ||
88 | |||
89 | ``` | ||
90 | Mon Jan 2 15:04:05 MST 2006 | ||
91 | ``` | ||
92 | |||
93 | Which is the date produced when you write the first seven | ||
94 | natural numbers like so: | ||
95 | |||
96 | ``` | ||
97 | 01/02 03:04:05 '06 -0700 | ||
98 | ``` | ||
99 | |||
100 | Parsing a string in `YYYY-MM-DD` format would look something | ||
101 | like: | ||
102 | |||
103 | ```go | ||
104 | const layout = "2006-01-02" | ||
105 | time.Parse(layout, "2020-08-01") | ||
106 | ``` | ||
107 | |||
108 | This so-called "intuitive" method of formatting dates | ||
109 | doesn't allow you to print `0000 hrs` as `2400 hrs`, it | ||
110 | doesn't allow you to omit the leading zero in 24 hour | ||
111 | formats. It is rife with inconveniences, if only there were | ||
112 | a [tried and | ||
113 | tested](https://man7.org/linux/man-pages/man3/strftime.3.html) | ||
114 | date formatting convention ... | ||
115 | |||
116 | ### Statements over Expressions | ||
117 | |||
118 | Statements have side effects, expressions return values. | ||
119 | More often than not, expressions are easier to understand at | ||
120 | a glance: evaluate the LHS and assign the same to the RHS. | ||
121 | |||
122 | Rust allows you to create local namespaces, and treats | ||
123 | blocks (`{}`) as expressions: | ||
124 | |||
125 | ```rust | ||
126 | let twenty_seven = { | ||
127 | let three = 1 + 2; | ||
128 | let nine = three * three; | ||
129 | nine * three | ||
130 | }; | ||
131 | ``` | ||
132 | |||
133 | The Go equivalent of the same: | ||
134 | |||
135 | ```go | ||
136 | twenty_seven := nil | ||
137 | |||
138 | three := 1 + 2 | ||
139 | nine := three * three | ||
140 | twenty_seven = nine * three | ||
141 | ``` | ||
142 | |||
143 | |||
144 | ### Erroring out on unused variables | ||
145 | |||
146 | Want to quickly prototype something? Go says no! In all | ||
147 | seriousness, a warning would suffice, I don't want to have | ||
148 | to go back and comment each unused import out, only to come | ||
149 | back and uncomment them a few seconds later. | ||
150 | |||
151 | ### Error handling | ||
152 | |||
153 | ```go | ||
154 | if err != nil { ... } | ||
155 | ``` | ||
156 | |||
157 | Need I say more? I will, for good measure: | ||
158 | |||
159 | 1. Error handling is optional | ||
160 | 2. Errors are propagated via a clunky `if` + `return` statement | ||
161 | |||
162 | I prefer Haskell's "Monadic" error handling, which is | ||
163 | employed by Rust as well: | ||
164 | |||
165 | ```rust | ||
166 | // 1. error handling is compulsory | ||
167 | // 2. errors are propagated with the `?` operator | ||
168 | fn foo() -> Result<String, io::Error> { | ||
169 | let mut f = File::open("foo.txt")?; // return here if error | ||
170 | let mut s = String::new(); | ||
171 | |||
172 | f.read_to_string(&mut s)?; // return here if error | ||
173 | |||
174 | Ok(s) // all good, return the value inside a `Result` context | ||
175 | } | ||
176 | ``` | ||
177 | |||