Apologies for try-ing

rocketlaunchr.cloud
4 min readJul 24, 2019

--

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:

Official try proposal document

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)

Links

--

--