Apologies for try-ing
One of the common criticisms of Go is the constant boilerplate code required to do repetitive tasks. It shows up in surveys time and time again: in fact, one of my pet peeves is not being able to inline multi-return value functions.
A few months ago, I proposed a must
function to alleviate the problem. The original proposal can be found here. The “Must” paradigm is already part of idiomatic Go so I thought my proposal would be a good fit for the Go language.
You can already see it used in the standard library to initialize package-level variables: https://golang.org/pkg/text/template/#Must and https://golang.org/pkg/regexp/#MustCompile
You can also eradicate unnecessary clutter such as here:
type ColorGroup struct {
ID int
Name string
Colors []string
}
group := ColorGroup{
ID: 1,
Name: "Reds",
Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}b, err := json.Marshal(group)
if err != nil {
fmt.Println("error:", err)
return
}os.Stdout.Write(b)
Since ColorGroup
conforms to the JSON specs, json.Marshal
will never return an error. It can then be replaced it with os.Stdout.Write(must(json.Marshal(group)))
.
Try Proposal
Unbeknownst to me, the top brass was at the time obsessed with fixing up Go’s error-handling “problem.” They would always cite surveys to back up the validity of their concern. I see absolutely no issues with the verbosity of Go’s error-handling because I always believed that the repetitive check code has a subconscious psychological impact that is profound and overall positive.
The top brass liked the approach of making must
into a builtin to maintain backward compatibility. They also liked the generics-compatible input arguments being transferred as return values, whilst the final argument had to be of type error
.
Next thing I know they adopted it for try
:
I emphasized to them that my proposal was not about error handling. If anything, it was about ignoring error handling. There is a subtle but important distinction.
The Go maintainers disagreed 😟
Because the Go maintainers interpreted my proposal as related to error-handling, they loosely coupled my proposal with the try
proposal:
Once the try
proposal was rejected, the must
proposal (which can exist independently) unfortunately got prematurely rejected as cannon fodder.
igo — An alternative Go transpiler
That’s why I added must
to the open-source igo project. igo (pronounced ee-gohr) is (in theory) a superset of the Go programming language.
It features new language features such as the Address Operators (&
) for constants and functions, defer
for inside for-loops and the must
function. These features make writing Go applications more readable and expressive.
To use these awesome features, you use igo to transpile your *.igo
files to standard *.go
files with one command. It adds an extra step to the build process (like Typescript → Javascript), but the results are worth it.
dbq — Barbeque the boiler plate code
Since everyone agrees that performing simple database queries in Go requires much cumbersome boilerplate code, in the spirit of the try proposal, I created dbq using igo. The dbq package is MySQL and PostgreSQL compatible and totally eradicates all the boilerplate code for database queries.
It is unbelievably Convenient and Developer Friendly.
Examples
Let’s assume a table called users
:
| id | name | age | created_at |
|----|-------|-----|------------|
| 1 | Sally | 12 | 2019-03-01 |
| 2 | Peter | 15 | 2019-02-01 |
| 3 | Tom | 18 | 2019-01-01 |
Bulk Insert
You can insert multiple rows at once.
db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/db")users := []interface{}{
[]interface{}{"Brad", 45, time.Now()},
[]interface{}{"Ange", 36, time.Now()},
[]interface{}{"Emily", 22, time.Now()},
}stmt := dbq.INSERT("users", []string{"name", "age", "created_at"}, len(users))dbq.E(ctx, db, stmt, nil, users)
Query
dbq.Q
ordinarily returns []map[string]interface{}
results, but you can automatically unmarshal to a struct. You will need to type assert the results.
type user struct {
ID int `dbq:"id"`
Name string `dbq:"name"`
Age int `dbq:"age"`
CreatedAt time.Time `dbq:"created_at"`
}
opts := &dbq.Options{ConcreteStruct: user{}, DecoderConfig:x}
results, err := dbq.Q(ctx, db, "SELECT * FROM users", opts)
Query Single Row
If you know that the query will return at maximum 1 row:
result := dbq.MustQ(ctx, db, "SELECT * FROM users LIMIT 1", dbq.SingleResult)
if result == nil {
// no result
} else {
result.(map[string]interface{})
}
MySQL cancelation
To properly cancel a MySQL query, you need to use the mysql-go package. dbq
plays nicely with it.
import (
sql "github.com/rocketlaunchr/mysql-go"
)pool, _ := sql.Open("user:password@tcp(localhost:3306)/db")conn, err := pool.Conn(ctx)
defer conn.Close()result := dbq.MustQ(ctx, conn, "SELECT * FROM users LIMIT 1", dbq.SingleResult)
if result == nil {
// no result
} else {
result.(map[string]interface{})
}
Final Notes
Have Fun! (and read the modified MIT license)