Golangのinterfaceに対する疑問を調べてみた
GolangでDIをしてみたい
ちゃんとまとめたかったが眠いのでコードだけおいておく(未完成
C#でSAPが書ける Blazorフレームワークを勉強したい
まず、私のWeb開発に対する意見なんだけど
ごめんなさい、スクリプト言語は私の中では5年前に終わった
Railsが出た当初は、こんなエコなシステム!RailsだけでWebはいいじゃん
って思った時期もあったんだけど、結局はコンパイルできないためテストコードいっぱい必要で開発速度アドバンテージ失い
PaaSの方がアドバンテージ高く
そしてスクリプトの実行速度の遅さに、決定的だったのがCPUがマルチコア化し、プロセス生成ではもうスペック出せない事
そこで、非同期フレームワークnodeが出たのだが、結局はnodeの速度限界とシングルスレッドしか扱えない事、言語構文から無理が出て
最終的には、Java(Scala)、Erlang(Elixir)、 C#、Go、Rust・・・ といった、非同期可能なコンパイル言語以外ないなーと思うように
その中から選ぶと
Scalaはコンパイル遅い。Javaは言語が古かった(最近のJavaはラムダも使えたり十分だが昔は一貫してオブジェクト指向で関数型要素が入ってこなかった)、Erlang(Elixir)はVMの速度が遅いという話で及び腰、Rustは開発がちょっと遅くて安定しない、C#はWindowsでしか動かないから論外
で、Goという選択肢を選んだ
が、最近はその状況もかわってきてる
Javaは最近言語をどんどん柔軟に関数型プログラミングを採用してきたので今は十分使える
Erlangは実際に評価してみないとな・・多分今は問題になるほど遅くはないと思う
www.atmarkit.co.jp
MicrosoftがRust をバックアップするとの事で状況は変わってきた
そして、.net Coreという、マルチプラットフォームで動作する .netフレームワークをMicrosoftが発表した!!
という事で、最も期待しているのがC#
そのなかで Blazorというフレームワークが面白そうだ
BlazorはC#でSPAを作れるらしい
なんとViewもC#で作れる
仕組みは、C#で書いてビルド時に .netのILからWebAssemblyに変換してブラウザで実行していると!
ってことで、Blazor注目!!
会社のホームページ変更とブログ移転の検討
www.murasame-lab.com
上記が今の会社のホームページとなっている
知人のWebデザイナーが仕事が暇というので仕事をしてもらったが、その後体調を崩してメンテがしてもらえていない
そのため、会社のホームページを一新しようと思っている
とりあえず、2年ほど前から海外移住した友達のWebデザイナーに打診中だが
断られたら、ひとまずWordPressで作ろうと思う(実は今のページを作る前にモックでWPで作ったのがある)
それに加え、自社ドメインを強くするためにも、ブログを自ドメインにしようとおもう
はてなブログはProにすれば独自ドメインにすることが可能だが
もしかしたら自社のWordPressに変更するかもしれない
移行手順はもう完璧だ
とりあえず、少しずつ会社らしくしていこう
Go言語にデストラクタがないので何とかしたい
Goの真実
Goにはコンストラクタもデストラクタもない
ので、便宜上 コンストラクタに当たる関数は 構造体名の頭にNewのプリフィクスを入れた関数である
C言語だと init_hoge とするのが NewHogeになっただけだ
そして、そのNew関数は構造体のポインタを返す(Cでも呼び方わすれたが構造体をClassっぽく書くときに同じことをする)
結局C言語なのだ(念のため、C++ではない)
そしてデストラクタがないので、呼び出し側が終了関数を明示的に呼ばなければならない
Cだと delete_hoge とか release_hoge、teardown_hoge・・・ 呼び名が統一されてる覚えがない
Goにもデストラクタとして呼び名が統一されている気がしない
つまり、よく呼び忘れてメモリリークする!!
https://play.golang.org/p/i6-vTaTVx9Z
package main import ( "fmt" ) type File struct{ filename string } func NewFile(f string) *File{ fmt.Printf("File{%s} new&open\n", f) return &File{f} } func(s *File)Read(){ fmt.Printf("File{%s} read\n", s.filename) } func(s *File)Close(){ fmt.Printf("File{%s} close\n", s.filename) } func main() { fmt.Println("main start") file := NewFile("test") file.Read() fmt.Println("main finish") } main start File{test} new&open File{test} read main finish
上記のコードはRAII原則にのっとり、New時にリソースをOpenしているが
残念なことにライブラリを使う人がClose()を呼び忘れたため、リソースリークしている
defer
いちおうGoにはdeferがあり、関数が終了した時に実行されるファイナライザを書くことができる
https://play.golang.org/p/LPEm7oDSf2M
... file := NewFile("test") defer file.Close() file.Read() ... main start File{test} new&open File{test} read main finish File{test} close
これは便利だが、このdeferはライブラリを使う側の人間が忘れたらCloseしてくれない
構造体の方でdeferを入れたい
そう考えるかもしれないが、deferはあくまで、関数の終了時のファイナライザだ
コンストラクタにdeferを入れると当然コンストラクトした後にすぐCloseが走る
https://play.golang.org/p/aav2psenFxF
... func NewFile(f string) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close() return &file } ... main start File{test} new&open File{test} close File{test} read main finish
runtime.SetFinalizer
見るからに悪手だが、ランタイムライブラリに、ガーベージコレクションが走った時に実行されるコールバックがある
x:= "hoge" runtime.SetFinalizer(&x, func(x *string){fmt.Println("SetFinalizer")}) // 上記だと即終了しGCが走らないので故意にGCを走らせ待機させる
と、上記では xがGCされる時に 次のラムダ式が呼ばれる。ラムダ式の引数はGCされるオブジェクトのポインタだ
https://play.golang.org/p/QYkVhC1uQKP
これを構造体に当てはめられないか?
コンストラクタでSetFinalizerする
コンストラクタで構造体を作成し、ポインタを使う側に返すが、このポインタにSetFinalizerを設定し
構造体がGCされるときにCloseしてみよう
念のためインスタンスを複数作る
https://play.golang.org/p/LL3ejYYcowe
package main import ( "fmt" "runtime" "time" ) type File struct{ filename string } func NewFile(f string) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} runtime.SetFinalizer(&file, func(f *File){f.Close()}) return &file } func(s *File)Read(){ fmt.Printf("File{%s} read\n", s.filename) } func(s *File)Close(){ fmt.Printf("File{%s} close\n", s.filename) } func(s *File)Nop(){ fmt.Printf("File{%s} nop\n", s.filename) } func hoge(){ file := NewFile("test") file.Read() file2 := NewFile("test2") file2.Read() file3 := NewFile("test3") file3.Nop() } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read File{test2} new&open File{test2} read File{test3} new&open File{test3} nop File{test3} close File{test2} close File{test} close main finish
綺麗に動いてしまった・・・
レシーバーにSetFinalizerする
先ほどはコンストラクタにSetFinalizerをしたので、たぶん確実にFinalizerが走るが
特定メソッドで、レシーバーに対してSetFinalizerしたらどうなるか?
たとえばReadメソッドでやってみる
func(s *File)Read(){ runtime.SetFinalizer(s, func(f *File){f.Close()}) fmt.Printf("File{%s} read\n", s.filename) } main start File{test} new&open File{test} read File{test2} new&open File{test2} read File{test3} new&open File{test3} nop File{test2} close File{test} close main finish
すばらしい、意図したとおりに動いた(file3はReadメソッドを読んでないのでFinalizeされてない)
使うシーンがあるか不明だが、例えばOpenメソッドを呼んだ時だけCloseのFinalizerを設定
という使い方も不可能ではなさそうだ
でもね
GCされるまでは開放されないので、もし長時間GCされずに存在していたら
その間ファイルを開きっぱなしだったり、ネットワークリソースを開放しない事になる
だから、SetFinalizerでデストラクターをするのは、よくない事は明確である・・
どこを間違えたかと思ったが、Goはオブジェクト指向言語ではないというのが答えだ
本当は関数型のように書くべきなんだろうか?
Callback にすべきではないか?
コンストラクタでdeferしても、コールバックで続きの処理を行えば
コンストラクタのコンテキスト内なので問題ないはずだ・・・
https://play.golang.org/p/1ZBphVFMHKM
package main import ( "fmt" "runtime" "time" ) type File struct{ filename string } func NewFile(f string, cb func(file *File)*File) *File{ // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close(nil) if(cb != nil){ return cb(&file) } return &file } func(s *File)Read(cb func(file *File)*File)*File{ fmt.Printf("File{%s} read\n", s.filename) if(cb != nil){ return cb(s) } return s } func(s *File)Close(cb func(file *File)*File)*File{ fmt.Printf("File{%s} close\n", s.filename) if(cb != nil){ return cb(s) } return s } func(s *File)Nop(cb func(file *File)*File)*File{ fmt.Printf("File{%s} nop\n", s.filename) if(cb != nil){ return cb(s) } return s } func hoge(){ _ = NewFile("test", func(file *File)*File{ file.Read(func(file *File)*File{ fmt.Println("callback hell") return file }) return file }) } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read callback hell File{test} close main finish
やったぜ!!(やりたくない
もう少し実用的に
ReadやWriteの引数ちゃんと作って、もう少し実用的にします
エラーも返さないとね(エラー処理は省略
https://play.golang.org/p/XbAWauNUNyZ
package main import ( "fmt" "runtime" "time" ) type File struct { filename string } func NewFile(f string, cb func(file *File) (*File, error)) (*File, error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer Close(&file, nil) if cb != nil { return cb(&file) } return &file, nil } func Read(file *File, len int, cb func(file *File, readed *string) (*File, error)) (*File, error) { fmt.Printf("File{%s} read\n", file.filename) data := "readed" if cb != nil { return cb(file, &data) } return file, nil } func Write(file *File, data *string, cb func(file *File, written int) (*File, error)) (*File, error) { written := len(*data) fmt.Printf("File{%s} write {%s} size={%d}\n", file.filename, *data, written) if cb != nil { return cb(file, written) } return file, nil } func Close(file *File, cb func(file *File) (*File, error)) (*File, error) { fmt.Printf("File{%s} close\n", file.filename) if cb != nil { return cb(file) } return file, nil } func Nop(file *File, cb func(file *File) (*File, error)) (*File, error) { fmt.Printf("File{%s} nop\n", file.filename) if cb != nil { return cb(file) } return file, nil } func hoge() { _, _ = NewFile("test", func(file *File) (*File, error) { Read(file, 5, func(file *File, readed *string) (*File, error) { fmt.Printf("READ{%s}\n", *readed) data :="hogehoge" Write(file, &data, func(file *File, written int) (*File, error) { fmt.Printf("WRITE [%d]bytes\n", written) return file, nil }) return file, nil }) return file, nil }) } func main() { fmt.Println("main start") hoge() runtime.GC() time.Sleep(1 * time.Second) fmt.Println("main finish") } main start File{test} new&open File{test} read READ{readed} File{test} write {hogehoge} size={8} WRITE [8]bytes File{test} close main finish
ライブラリを使う側からはもうCloseの事考えなくてよくなりました
やったね!!
しかし、コールバック地獄、エラー処理の場所・・・もっと考えなければならない事が増えてしまいました
しかもGoはJavaScriptやC#のような、クロージャを簡単に書く書式もないので、まいかいfunc ()と大変
コールバック必要なのはNewだけだったんや!
別に非同期プログラミングのようなコールバックする必要はない
GoはGoroutineという素晴らしい仕組みがあるので、同期っぽく書けばいい
わざわざ人間に不親切なコールバック地獄にする必要がない
コールバックを使った理由はNew関数を抜けるときにdeferdでCloseし忘れを防ぎたい
処理が終わるまでNew関数を抜けないためだ
ReadやWriteまでコールバックする必要がない。Newだけでよかったんだ
https://play.golang.org/p/WtixVskDI7R
package main import ( "fmt" ) type File struct { filename string } func NewFile(f string, cb func(file *File)error) ( error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} defer file.Close() err := cb(&file) return err } func (s *File) Read(){ fmt.Printf("File{%s} read\n", s.filename) } func (s *File) Close() { fmt.Printf("File{%s} close\n", s.filename) } func (s *File) Nop() { fmt.Printf("File{%s} nop\n", s.filename) } func hoge() { _ = NewFile("test", func(file *File)error{ file.Read() return nil }) } func main() { fmt.Println("main start") hoge() fmt.Println("main finish") } main start File{test} new&open File{test} read File{test} close main finish
当然ちゃんとクローズされるし、コールバックは初回のNewだけなので、コードの見通しも悪くない
ただ、コールバックとして全てを中にいれなければいけない
オブジェクト指向脳だと自由に構造体を使い回したいと思うだろう
コールバックとオブジェクト指向のハイブリッド
Goにはメソッドのオーバーライドやデフォルト引数がない
可変長引数で処理できるが今回はそこは手抜きで、CallbackがnilだとClose管理はライブラリでは行わない
https://play.golang.org/p/M6g-dqUz86P
package main import ( "fmt" ) type File struct { filename string } func NewFile(f string, cb func(file *File) error) (*File, error) { // file Open処理をする fmt.Printf("File{%s} new&open\n", f) file := File{f} var err error = nil if cb != nil { defer file.Close() err = cb(&file) } return &file, err } func (s *File) Read() { fmt.Printf("File{%s} read\n", s.filename) } func (s *File) Close() { fmt.Printf("File{%s} close\n", s.filename) } func (s *File) Nop() { fmt.Printf("File{%s} nop\n", s.filename) } func hoge() { // クロージャで自動的にCloseする _,_ = NewFile("test", func(file *File) error { file.Read() return nil }) // クローズは使う側が責任持つ file2, _ := NewFile("test2", nil) defer file2.Close() file2.Read() // 忘れると当然リークします file3, _ := NewFile("test3", nil) // defer file3.Close() file3.Read() } func main() { fmt.Println("main start") hoge() fmt.Println("main finish") } main start File{test} new&open File{test} read File{test} close File{test2} new&open File{test2} read File{test3} new&open File{test3} read File{test2} close main finish
結局どれがいいのか?
Finalizerは動作はするが、GCが走るまで開放が行われないので実用的ではない
コールバック地獄はダメ!絶対!(おそらくGoはGoroutineで同期的に書く設計なので、promise/futureやasync/awaitのようなコールバック解決ライブラリはあまり出ない)
Newのコールバックは場合によっては使えると思う
でも基本は、ライブラリを使う側が責任もって後始末しなければいけないっぽい
ハイブリッドは良さそう
デスクトップPCのOS入れ直し
デスクトップPCを新調した
5月にデスクトップPCを超パワーアップした
CPUを corei9 9700Kにし
メモリを32GB
マザーはSLI対応
ビデオはGeforce1080Tiのまま
ところが1つだけ失敗がある
SSDを交換するの忘れた
ので買う事になった
現在ドライブは
SSD 256GB + HDD 4TBだ
OSが30GB、開発ツールが100GBほど使うと残りは100GB程度
結構厳しい
そのため、現在はゲームは全部HDDドライブに入れている
なんのためのゲーミングPCだ・・
という事で、近いうちに1TB~のSSDを買い、OSを移そうと思っている
アクティベートとか面倒化と思っていたが
どうやらSSD入れ替え程度ではOSの再アクティベートも不要で
イメージソフトを使えばイメージコピーすれば、特にインストールし直す事なく使えそうだ
非常にたすかる
うちのマザーは M.2を3本までさせるらしい
1個しか刺さない予定だけど
これで今まで悩まされていた容量問題が解決する