Python comparisons can be chained like:
This means:
- x is evaluated
- y is evaluated
- if x < y then z is evaluated, else skip master if body
- if y <= z then flow proceeds into master if body
This is intuitive and easier to read. This proposal covers monotone relations only (<,<=,>,>= in one direction) like:
x < y <= za > b > c
It does not cover any non-monotone relations like:
p >= q < rq < w != ea != s == d
In order to determine places where such chained interval comparisons could be used in Go, we can use an (improved) bash script like:
# Finds 3 component monotone comparisons
name='( [a-zA-Z_][a-zA-Z_0-9.*/+%()-]* )'
nmrf='(?:\1|\2)'
less='<=?'
more='>=?'
keyw='(?:if|for|case).*'
logi='[^|&]*(?:&&|\|\|)[^|&]*'
patl=(
"$keyw(?:$less$name|$name$more)$logi(?:$nmrf$less|$more$nmrf)"
"$keyw(?:$more$name|$name$less)$logi(?:$nmrf$more|$less$nmrf)"
)
s=0
for pat in "${patl[@]}"; do
r=$(grep -Pr --include '*.go' "$pat" . | grep -c .)
let s+=r
grep -Prm 1 --include '*.go' "$pat" . | head -n 3
done
echo "$s+ cases"
On some popular projects developed in Go, we get the following examples & totals:
examples from dgraph:
./chunker/json_parser.go: if buf.batchSize > 0 && len(buf.nquads) >= buf.batchSize {
./chunker/rdf_state.go: case r >= 'a' && r <= 'z':
./gql/parser.go: if depth > 4 || depth < 0 {
./compose/compose.go: if opts.NumZeros < 1 || opts.NumZeros > 99 {
./dgraph/cmd/zero/tablet.go: if tab.Space <= sizeDiff/2 && tab.Space > size {
./lex/lexer.go: if p.idx < 0 || p.idx >= len(p.l.items) {
448+ cases
examples from etcd:
./client/client_test.go: if ratio := float64(pinNum) / float64(round); ratio > max || ratio < min {
./clientv3/client.go: if cfg.MaxCallRecvMsgSize > 0 && cfg.MaxCallSendMsgSize > cfg.MaxCallRecvMsgSize {
./functional/runner/global.go: for rc.progress < rounds || rounds <= 0 {
./auth/store.go: if bcryptCost < bcrypt.MinCost || bcryptCost > bcrypt.MaxCost {
./integration/v3_lease_test.go: if ttlresp.TTL < expectedTTL-1 || ttlresp.TTL > expectedTTL {
./mvcc/kvstore_txn.go: if limit <= 0 || limit > len(revpairs) {
276+ cases
examples from frp:
./vendor/github.com/fatedier/beego/logs/logger.go: case code >= 200 && code < 300:
./vendor/github.com/fatedier/kcp-go/kcp.go: if n >= int(kcp.mtu-IKCP_OVERHEAD) || n < 0 {
./vendor/github.com/gorilla/websocket/conn.go: if c.readLimit > 0 && c.readLength > c.readLimit {
./vendor/github.com/golang/snappy/decode_other.go: if offset <= 0 || d < offset || length > len(dst)-d {
./vendor/github.com/gorilla/websocket/x_net_proxy.go: if port < 1 || port > 0xffff {
./vendor/github.com/hashicorp/yamux/session.go: if mt < typeData || mt > typeGoAway {
219+ cases
examples from gitea:
./models/git_diff.go: if begin <= line && end >= line {
./modules/auth/auth.go: if token.ExpiresAt < time.Now().Unix() || token.IssuedAt > time.Now().Unix() {
./modules/git/signature.go: if firstChar >= 48 && firstChar <= 57 {
./models/action.go: if slashIndex < 0 || slashIndex >= poundIndex {
./models/repo_collaboration.go: if mode <= AccessModeNone || mode > AccessModeOwner {
./modules/indexer/repo.go: if startIndex < 0 || locationStart < startIndex {
590+ cases
examples from go:
./misc/cgo/testshared/shared_test.go: if prog.Off <= offset && offset < prog.Off+prog.Filesz {
./src/archive/tar/format.go: if 148 <= i && i < 156 {
./src/bufio/bufio.go: if n > 0 && n < b.n {
./misc/cgo/gmp/gmp.go: if base < 2 || base > 36 {
./src/archive/tar/strconv.go: if perr != nil || n < 5 || int64(len(s)) < n {
./src/archive/zip/reader.go: if o := int64(d.directoryOffset); o < 0 || o >= size {
1369+ cases
examples from influxdb:
./bolt/bucket.go: if limit > 0 && len(bs) >= limit {
./bolt/dashboard.go: if limit > 0 && len(ds) >= limit {
./chronograf/oauth2/cookies.go: if lifespan > 0 && inactivity > lifespan {
./chronograf/influx/queries/select.go: if got := len(v.Args); got < 1 || got > 2 {
./chronograf/oauth2/mux_test.go: if resp.StatusCode < 300 || resp.StatusCode >= 400 {
./cmd/influx/task.go: if taskFindFlags.limit < 1 || taskFindFlags.limit > platform.TaskMaxPageSize {
53+ cases
examples from kubernetes:
./cmd/kubeadm/app/preflight/checks.go: if r != nil && r.StatusCode >= 500 && r.StatusCode <= 599 {
./cmd/kubeadm/app/util/endpoint.go: if err == nil && (1 <= portInt && portInt <= 65535) {
./cmd/kubeadm/app/util/system/package_validator.go: case c >= '0' && c <= '9':
./cmd/kube-apiserver/app/options/validation.go: if options.KubernetesServiceNodePort < 0 || options.KubernetesServiceNodePort > 65535 {
./cmd/kube-scheduler/app/options/insecure_serving.go: if o.BindPort < 0 || o.BindPort > 65535 {
./pkg/apis/core/validation/validation.go: if iscsi.Lun < 0 || iscsi.Lun > 255 {
1524+ cases
examples from moby:
./client/request.go: if serverResp.statusCode >= 200 && serverResp.statusCode < 400 {
./daemon/daemon_unix.go: if resources.Memory > 0 && resources.MemorySwap > 0 && resources.MemorySwap < resources.Memory {
./daemon/events/events.go: if untilNanoUnix > 0 && ev.TimeNano > untilNanoUnix {
./builder/dockerfile/evaluator.go: if i < 0 || i > len(r.flat) {
./builder/remotecontext/remote.go: if plen <= 0 || plen > maxPreambleLength {
./daemon/cluster/listen_addr.go: if portNum < 1024 || portNum > 49151 {
461+ cases
examples from nomad:
./client/fs_endpoint.go: if limit > 0 && limit < streamFrameSize {
./command/agent/alloc_endpoint.go: if len(tokens) > 2 || len(tokens) < 1 {
./command/agent/retry_join.go: if serverJoin.RetryMaxAttempts > 0 && attempt > serverJoin.RetryMaxAttempts {
./api/internal/testutil/freeport/freeport.go: if port < firstPort+1 || port >= firstPort+blockSize {
./client/stats/cpu_test.go: if percent < expectedPercent && percent > (expectedPercent+1.00) {
./command/agent/config.go: if 0 > port || port > 65535 {
769+ cases
examples from prometheus:
./documentation/examples/remote_storage/remote_storage_adapter/opentsdb/tagvalue.go: case b >= '0' && b <= '9':
./pkg/textparse/openmetricslex.l.go: case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
./pkg/textparse/promlex.l.go: case c == ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
./cmd/prometheus/main.go: if cfg.tsdb.WALSegmentSize < 10*1024*1024 || cfg.tsdb.WALSegmentSize > 256*1024*1024 {
./pkg/textparse/openmetricslex.l.go: case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
./pkg/textparse/promlex.l.go: case c >= '0' && c <= ':' || c >= 'A' && c <= 'Z' || c == '_' || c >= 'a' && c <= 'z':
550+ cases
examples from terraform:
./config/interpolate_funcs.go: if i >= from && i < to {
./configs/variabletypehint_string.go: case 76 <= i && i <= 77:
./helper/resource/state.go: if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second {
./config/resource_mode_string.go: if i < 0 || i >= ResourceMode(len(_ResourceMode_index)-1) {
./configs/configschema/internal_validate.go: case blockS.MinItems < 0 || blockS.MinItems > 1:
./configs/configschema/nestingmode_string.go: if i < 0 || i >= NestingMode(len(_NestingMode_index)-1) {
653+ cases
As seen from 6912+ cases above, many thousands of if / case / for clauses doing interval checks can be made simpler and easier to read. Also, it has advantages for IEEE floats, see below.
What do you think?