ココ最近Goを勉強しはじめて
Go力(ごうりき) 低いですが ちょっとずつ勉強していきます
#Genericsについて
GolangにはGenericsはありません
Genericsとは大雑把にいうと 色々な型での共通処理を1つのコードで行います
例えば、Listとかのコンテナライブラリが有名ですね
本来なら型ごとに
IntList ilist;
ilist.insert(1);
StringList slist;
slist.insert("hoge");
DoubleList dlist;
dlist.insert(1.0);
class IntList{
private:
int *begin;
int *end;
public:
void insert(int);
...
;
class StringList{
private:
string *begin;
string *end;
public:
void insert(string);
...
;
class DoubleList{
private:
double *begin;
double *end;
public:
void insert(double);
...
};
のように 型ごとに関数を作る必要があるのが
List<int> ilist;
ilist.add(1);
List<string> slist;
slist.add("hoge");
List<double> dlist;
dlist.add(1.0);
template<class T>
class List<T>{
private:
T *begin;
T *end;
public:
void insert(T);
...
};
と、複数型があっても 1つの関数書けばよくなり
開発工数もメンテナンス性もあがり とても便利
ところがGoには ジェネリクスがありません!!
Go2になればジェネリクスが導入されるという噂はありますが
現時点では存在しないので なんとかしたいところです
そういうのは interfaceを使えば出来そうなので 勉強ついでに 作ってみようと思いました
#Contains
今回は Containsを作ろうと思います
与えられた配列内に特定の要素が存在すればTrue,しなければFalse を返します
##IntVal作戦
パッと思いついた作戦です。
Goにはclassはありませんが interfaceと structがあります
継承はないけど ダックタイピングができます
ということで
ベースのInterfaceを作り、ダックタイピングしてみます
type Traits interface {
Val() Traits
}
type IntVal struct{val int}
func (u IntVal) Val() Traits {
return (u)
}
type StringVal struct{val string}
func (u StringVal) Val() Traits {
return (u);
}
func Contains(arr []Traits, val Traits) bool{
for _, v := range arr{
if v == val{
return true
}
}
return false
}
va := []Traits {IntVal{1},IntVal{2},IntVal{3},IntVal{4}}
log.Printf( strconv.FormatBool( Contains( va, IntVal{1})) )
log.Printf(strconv.FormatBool(Contains( va, IntVal{100})))
vs := []Traits {StringVal{"1"},StringVal{"2"},StringVal{"3"},StringVal{"4"}}
log.Printf( strconv.FormatBool( Contains( vs, StringVal{"1"})) )
----
2018/12/02 03:10:15 true
2018/12/02 03:10:15 false
2018/12/02 03:10:15 true
IntVal、StringValという型?を作り interfaceで処理するという
最初に思いついたやりかたですが
不満ありますよね
Goには型変換コンストラクタがないので
va := []Traits {IntVal{1},IntVal{2},IntVal{3},IntVal{4}}
と いちいち 明示的に型変換が必要で面倒
##interfaceで抽象化すればええやん?
更に抽象化をすすめる
実は IntValとかは作らなくてよかった(メタプロの癖)
func Contains(arr []interface{}, val interface{}) bool{
for _, v := range arr{
if v == val{
return true
}
}
return false
}
va := []int {1, 2, 3, 4}
log.Printf( strconv.FormatBool( Contains( va, 1)) )
log.Printf(strconv.FormatBool(Contains( va, 100)))
interface最高! ジェネリクスなくてもなんとかなりそう
と思うだろ?
“cannot use va (type []int) as type []interface {} in argument to Contains”
任意の型から interface{}への変換はできるが
配列から []interface{}への変換は ダメだそう
メモリマップの問題なのか いちど []interface{}配列へコピーする必要がある
va := []int {1, 2, 3, 4}
list := make([]interface{}, 0)
for _, v := range va {
list = append(list, v)
}
log.Printf( strconv.FormatBool( Contains( list, 1)) )
log.Printf(strconv.FormatBool(Contains( list, 100)))
vs := []string {"1", "2", "3", "4"}
list = make([]interface{}, 0)
for _, v := range vs {
list = append(list, v)
}
log.Printf( strconv.FormatBool( Contains( list, "1")) )
2018/12/02 04:16:57 true
2018/12/02 04:16:57 false
2018/12/02 04:16:57 true
いちどコピーしなおせば動くけどめんどくさい・・・
##配列コピーUtil
func CopyInt(arr []int) []interface{} {
list := make([]interface{}, 0)
for _, v := range arr {
list = append(list, v)
}
return list
}
func CopyString(arr []string) []interface{} {
list := make([]interface{}, 0)
for _, v := range arr {
list = append(list, v)
}
return list
}
func Contains(arr []interface{}, val interface{}) bool{
for _, v := range arr{
if v == val{
return true
}
}
return false
}
va := []int {1, 2, 3, 4}
log.Printf( strconv.FormatBool( Contains( CopyInt(va), 1)) )
log.Printf(strconv.FormatBool(Contains( CopyInt(va), 100)))
vs := []string {"1", "2", "3", "4"}
log.Printf( strconv.FormatBool( Contains( CopyString(vs), "1")) )
2018/12/02 04:16:57 true
2018/12/02 04:16:57 false
2018/12/02 04:16:57 true
Copyする関数をライブラリ化したよ。でも []interface{}が渡せないので型ごとにCopy関数作るよ
まー かなりましになったけど、色々と惜しいよ
型が増えるごとにCopy関数作らないとだめだし
Copy発生して 効率わるい
このへんが GenericsのないGoの限界なのか??
##switchでCopyまとめる
配列自体を interface{}で受け取り それを型でSwitch Caseで処理分岐させる
func Copy(arr interface{}) []interface{} {
dest := make([]interface{}, 0)
switch val := arr.(type) {
case []interface{}:
case []int:
for _, v := range val {
dest = append(dest, v)
}
case []string:
for _, v := range val {
dest = append(dest, v)
}
case int:
dest = append(dest, val)
case string:
dest = append(dest, val)
}
return dest
}
va := []int {1, 2, 3, 4}
log.Printf( strconv.FormatBool( Contains( Copy(va), 1)) )
log.Printf(strconv.FormatBool(Contains( Copy(va), 100)))
vs := []string {"1", "2", "3", "4"}
log.Printf( strconv.FormatBool( Contains( Copy(vs), "1")) )
2018/12/02 04:53:05 true
2018/12/02 04:53:05 false
2018/12/02 04:53:05 true
呼び出し側からは Copyメソッド1つで扱えるので便利だ
Switch CaseのdefaultでCopyしたいと思うが どうやらGoでは defaultでは foreachさせてもらえないので
型ごとに書くしか無い
つまり 型が増えると この関数を修正しなければならない
##ContainsにCopyをまとめる
曖昧になってあまり好きではないが Containsに渡すものすら interface{}にしてしまう
func Copy(arr interface{}) []interface{} {
dest := make([]interface{}, 0)
switch val := arr.(type) {
case []interface{}:
case []int:
for _, v := range val {
dest = append(dest, v)
}
case []string:
for _, v := range val {
dest = append(dest, v)
}
case int:
dest = append(dest, val)
case string:
dest = append(dest, val)
}
return dest
}
func Contains(i interface{}, val interface{}) bool{
arr := Copy(i)
for _, v := range arr{
if v == val{
return true
}
}
return false
}
va := []int {1, 2, 3, 4}
log.Printf( strconv.FormatBool( Contains( va, 1)) )
log.Printf(strconv.FormatBool(Contains( va, 100)))
vs := []string {"1", "2", "3", "4"}
log.Printf( strconv.FormatBool( Contains( vs, "1")) )
2018/12/02 05:01:30 true
2018/12/02 05:01:30 false
2018/12/02 05:01:30 true
エラー処理(アサーションしたり) などを考えなければ かなりましになってきた
いまだにCopyにおいて 型が増えるとライブラリコード追加というダメなんだけど
これが ジェネリクスのないGolangの限界かな?
##型チェック
本来ならGenericsを使っていれば厳密な型チェックができるため
Contain(int[], int) はOKであるが
Contain(int[],string) はコンパイル時にエラーさせられる
template<class T>
bool Contain(T[], T);
他の言語でも上記のように 2つの引数の型が厳密に定義できるが
Goでは 両方共interfaceでなんでも入ってしまうため
va := []int {1, 2, 3, 4}
log.Printf( strconv.FormatBool( Contains( va, "1")) )
と型をミスしてしまうと、この場合 コンパイルエラーも実行時エラーもでず Falseになる
のでContainsにて型チェックを入れてみる
本来ならこういう場合には Assertを使い、デバッグビルド時には例外が発生するが本番コードには影響あたえないようにするが
GolangにはAssertがないため Panicで問答無用でおとす
このあたりGo開発者は
Assertが便利なのは認めるが お前らは正しく使わないだろ? エラー処理を正しく使わないだろ?
だからGoにはAssertは入れないぜ
とのことで、正しく使いたいのに困った。。
func Contains(i interface{}, val interface{}) bool{
arr := Copy(i)
if(reflect.TypeOf(arr[0]) != reflect.TypeOf(val)){
log.Printf("missmatch Type in Contains [%s] and [%s]",reflect.TypeOf(arr[0]) ,reflect.TypeOf(val) )
panic(0)
}
for _, v := range arr{
fmt.Println( reflect.TypeOf(arr), reflect.TypeOf(v), reflect.TypeOf(val))
if v == val{
return true
}
}
return false
}
本来なら 引数の配列と 検索要素を比較して []intとint であればOK
という形にしたいが Goには配列の要素型を比較する方法がみつからず
また []interface{} になっており比較ができなかったので 1つ目の要素と比較しているが
意図通り 型間違えると panicをおこす
#オチ
https://golang.org/src/container/list/list.go
container/list 使いましょう