20201112のGoに関する記事は5件です。

Goクイズ Advent Calendar 2020/12/2

この記事は Goクイズアドベントカレンダー2020の記事です。

問題

次のコードを実行するとどうなるでしょうか?

package main

import "fmt"

type S []int

func main() {
    var a, b interface{}
    a, b = []int{}, S{}
    fmt.Print(a == b)
}

選択肢

  1. trueと表示される
  2. falseと表示される
  3. build error
  4. panic


解答と解説

正解: 2. false

本問に関係する主なGo言語仕様: 比較演算子 https://golang.org/ref/spec#Comparison_operators

TL;DR

  • function型、 slice型、および map 型の値は比較可能ではなく、比較演算子==および!=のオペランドになることができない。
    • ただし例外として、事前宣言された識別子としてのnilとは比較することができる。
  • interface型は比較可能で、a == bは次のようにふるまう。
    • abの動的な型と動的な値とが両方等しいときにのみtrueとなる。
    • abの動的型が等しくないときは、falseとなる。
    • abの動的型が等しく、かつその型の値が比較可能でないときには、run-time panicを引き起こす。

細かい解説

出題ポイントと直接関係ない部分を含めて、仕様書に基づいて順番に読んでいきます。適宜仕様書の該当箇所へのリンクを貼っているので、興味に応じて参照していただければと思います。

まず最初の行です。

var a, b interface{}

これは変数宣言で、interface{}という型を持つ変数a, bを宣言しています。interface{}は「何でも代入できる型」としてお馴染みだと思いますが、これをあえて仕様に基づいて説明すると、メソッドセットが空集合であるようなinterface型を表す型リテラルです。

次の行に進みます。

a, b = []int{}, S{}

これは代入文で、変数a, b[]intおよびSComposite literalを代入しています。この代入文が合法的であることは、次の代入可能性の仕様からわかります:

T is an interface type and x implements T.

Tがinterface型であり、値xがTを実装するとき(には、xをT型の変数に代入可能である)

「実装する(implement)」の定義はこちらにあります:

A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface.

interface型の変数は、そのmethod setを含むmethod setを持つ型の値を保持することができます。そのような型は、そのinterfaceを実装する、と言い表します。

interface{}型は、「空の」method setを表すinterface型です。どんな型のmethod setであれ、「空の」method setを含みますから、どんな型もinterface{}型を実装すると言えます。したがって、どんな型の値も、interface{}型の変数に代入可能です。これでこの行も問題ないことがわかりました。

問題は次の行です。

fmt.Print(a==b)

fmt.Printは、引数を(デフォルトのフォーマットで)標準出力に出力します。問題はa==bという式です。これについては比較演算子のセクションに説明されています。少し長くなりますが、問題に関係する部分を抜き出して訳してみます。

Comparison operators
Comparison operators compare two operands and yield an untyped boolean value.

比較演算子は、2つのオペランドを比較して、型なしboolean値を返します。

In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.

どのような比較においても、1つめのオペランドは2つめのオペランドの型に代入可能でなければならず、2つめのオペランドも1つめのオペランドの型に代入可能でなければなりません。

この問題ではabどちらの型もinterface{}型なので、お互いに代入可能になっており、問題ありません。

The equality operators == and != apply to operands that are comparable. The ordering operators <, <=, >, and >= apply to operands that are ordered. These terms and the result of the comparisons are defined as follows:

等値演算子==, !=は、比較可能であるようなオペランドに適用されます。順序演算子 <, <=, >, >=は順序付けされたオペランドに適用されます。これらの用語と各種の比較演算の結果は次のように定義されます:

Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

interfaceの値は比較可能です。2つのinterfaceの値が等しいのは、それらが同一の動的型を持ち、かつ、等しい動的な値をもつときか、あるいは両方がnilであるときです。

A comparison of two interface values with identical dynamic types causes a run-time panic if values of that type are not comparable.

2つのinterfaceの値であって同一の動的型をもつものを比較するとき、もしその型の値が比較可能でなければ、run-time panicを引き起こします。

Slice, map, and function values are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil.

Slice, map, functionの値は比較可能ではありません。ただし、例外として、slice、map、functionの値は、事前宣言された識別子のnilと比較することができます。

これを踏まえて、問題に戻ります。

type S []int
// ...省略
var a, b interface{}
a, b = []int{}, S{}
fmt.Print(a == b)

というようになっていましたから、aの動的型は[]int, bの動的型はSです。そしてSの基底型は[]intですので、これらの値はいずれも比較可能ではありません。それではpanicするのでしょうか?

そうではありません。defined typeであるSは、他のいかなる型とも異なる型であり(※1)、[]intSとはあくまでも異なる型なのです。a==bがpanicを引き起こすのは、あくまでも動的型が「同一」である場合に限ります。よって、この問題のように動的型が同一でない場合には、その型の値が比較可能かどうかを判断するまでもなく、abは等しくないと判断できて、a==bfalseとなります(※2)。

以上から、正解は2. のfalseとなります。

(※1): https://golang.org/ref/spec#Type_identity を見ると、"A defined type is always different from any other type." という記述があります。

(※2): 動的型が同一でないときにnot equalであるということは実は明示的には書かれていないのですが、これは未定義というわけではなくて、反対解釈的にnot equalと定義されている、というのが筆者の解釈です。


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】sqlxでIN句を含むクエリをNamedStmtで扱う

sqlxでIN句を含むクエリをNamedStmtで扱う方法のサンプルコードです。
EXISTS句にも応用可能です。
※エラーは想定していません

サンプルコード

main.go
func main() {
    userIds := []int{1, 2, 3, 4, 5}
    age := 20
    select(ids, createdAt)
}
select.go
var db *sqlx.DB

func select(userIds []int, age int) []User {

    // SQLの雛形
    originQuery := `SELECT * FROM USER WHERE USER_ID IN (:USER_ID) AND AGE > :AGE`

    // バインドする値
    arg := map[string]interface{}{
        "USER_ID":  userIds,
        "AGE": age,
    }

    // NamedStmtでIN句を使える形式にする
    query, params := formatQuery(&originQuery, &arg)

    // Select文を実行
    namedStmt, _ := db.PrepareNamedContext(context.Background(), *query)

    var users []User
    namedStmt.SelectContext(context.Background(), &sqlxSamples, *params)

    return users
}
format.go
func formatQuery(q *string, a *map[string]interface{}) (*string, *map[string]interface{}) {

    query, args, _ := sqlx.Named(*q, *a)
    fmt.Println(query) // SELECT * FROM USER WHERE USER_ID IN (?) AND AGE > ?
    fmt.Println(args)  // [[1 2 3 4 5] 20]

    query, args, _ = sqlx.In(query, args...)
    fmt.Println(query) // SELECT * FROM USER WHERE USER_ID IN (?, ?, ?, ?, ?) AND AGE > ?
    fmt.Println(args)  // [1 2 3 4 5 20]

    query = sqlx.Rebind(sqlx.NAMED, query)
    params := map[string]interface{}{}
    for i, arg := range args {
        key := fmt.Sprintf("arg%d", i+1)
        params[key] = arg
    }
    fmt.Println(query)  // SELECT * FROM USER WHERE USER_ID IN (:arg1, :arg2, :arg3, :arg4, :arg5) AND AGE > :arg6
    fmt.Println(params) // map[arg1:1 arg2:2 arg3:3 arg4:4 arg5:5 arg6:20]

    return &query, &params
}

予めSQLのバインド変数を?で定義しておけばsqlx.Named()は要らなくなるかも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】sqlxでIN句を含む条件をNamedStmtで扱う

sqlxでIN句を含む条件をNamedStmtで扱う方法のサンプルコードです。
EXISTS句にも応用可能です。
※エラーは想定していません

サンプルコード

main.go
func main() {
    userIds := []int{1, 2, 3, 4, 5}
    age := 20
    select(ids, createdAt)
}
select.go
var db *sqlx.DB

func select(userIds []int, age int) []User {

    // SQLの雛形
    originQuery := `SELECT * FROM USER WHERE USER_ID IN (:USER_ID) AND AGE > :AGE`

    // バインドする値
    arg := map[string]interface{}{
        "USER_ID":  userIds,
        "AGE": age,
    }

    // NamedStmtでIN句を使える形式にする
    query, params := formatQuery(&originQuery, &arg)

    // Select文を実行
    namedStmt, _ := db.PrepareNamedContext(context.Background(), *query)

    var users []User
    namedStmt.SelectContext(context.Background(), &sqlxSamples, *params)

    return users
}
format.go
func formatQuery(q *string, a *map[string]interface{}) (*string, *map[string]interface{}) {

    query, args, _ := sqlx.Named(*q, *a)
    fmt.Println(query) // SELECT * FROM USER WHERE USER_ID IN (?) AND AGE > ?
    fmt.Println(args)  // [[1 2 3 4 5] 20]

    query, args, _ = sqlx.In(query, args...)
    fmt.Println(query) // SELECT * FROM USER WHERE USER_ID IN (?, ?, ?, ?, ?) AND AGE > ?
    fmt.Println(args)  // [1 2 3 4 5 20]

    query = sqlx.Rebind(sqlx.NAMED, query)
    params := map[string]interface{}{}
    for i, arg := range args {
        key := fmt.Sprintf("arg%d", i+1)
        params[key] = arg
    }
    fmt.Println(query)  // SELECT * FROM USER WHERE USER_ID IN (:arg1, :arg2, :arg3, :arg4, :arg5) AND AGE > :arg6
    fmt.Println(params) // map[arg1:1 arg2:2 arg3:3 arg4:4 arg5:5 arg6:20]

    return &query, &params
}

予めSQLのバインド変数を?で定義しておけばsqlx.Named()は要らなくなるかも。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】文字列の中に特定の文字列が含まれているかをチェックする

概要

以下のような変数に特定の文字列が含まれているかをチェックする方法をメモしておく

package main

func main(){
  str := "abcde"
}

基本的にはstringsパッケージを使うことになるが、二つ方法がある

strings.Index

特定文字列が文字列の何文字目にあるかをチェックするメソッドだが特定文字列が含まれない場合は-1を返すのでそれを利用する

package main

import (
  "fmt"
  "strings"
)

func main(){
  str := "abcde"
  fmt.Print(strings.Index(str, "fg"))
  // 結果:-1
}

strings.Contains

strings.Indexを紹介したが、こちらはboolで返してくれるので基本的にはこっちを使用することになると思う

package main

import (
  "fmt"
  "strings"
)

func main(){
  str := "abcde"
  fmt.Print(strings.Contains(str, "fg"))
  // 結果:false
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirestoreとGoのデータ型変換の関係

ソース

Firestoreのデータ型

https://firebase.google.com/docs/firestore/manage-data/data-types?hl=ja

Go から Firestore のデータ型変換

Go Firestore
bool Bool
string String
int, int8, int16, int32 and int64 Integer
uint8, uint16 and uint32 Integer
uint, uint64 and uintptr 1だめ
float32 and float64 Double
[]byte Bytes
time.Time and 2*ts.Timestamp Timestamp
3*latlng.LatLng GeoPoint
Slices Array
*firestore.DocumentRef Reference
Maps and structs Map
あらゆる型のnil Null

Firestore から Go のデータ型変換

Firestore Go
Null nil
ブール値 (Bool) bool
文字列 (String) string
整数 (Integer) 4int64
浮動小数点数 (Double) 5float64
バイト (Bytes) []byte
日時 (Timestamp) time.Time
地理的座標 (GeoPoint) 3*latlng.LatLng
配列 (Arrays ) 6[]interface{}
マップ (Maps) 7map[string]interface{}
参照 (References) *firestore.DocumentRefs

  1. 整数型の基礎となるint64では表現できない値をもつ可能性があるため。 

  2. tsパッケージ:"github.com/golang/protobuf/ptypes/timestamp" 

  3. latlngパッケージ:"google.golang.org/genproto/googleapis/type/latlng" 

  4. 構造体フィールドにセットする場合、uint, uint64, uintptr 以外の整数型(signedやunsignedによらず)が許可されます。オーバーフローはエラーとして検出されます。 

  5. 構造体フィールドにセットする場合、float32も許可されます。オーバーフローはエラーとして検出されます。 

  6. 構造体フィールドにセットする場合、任意の型のsliceまたはarrayとして、再帰的に入力されます。Sliceは入力値に合わせてリサイズされます。配列(array)では余った要素は0で満たされ、逆に配列が短すぎる場合は入力値の余りは削除されます。 

  7. 構造体フィールドにセットする場合、キーは文字列のみ、値は任意の型が許可され、再帰的に入力されます。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む