- 投稿日:2019-07-14T15:26:32+09:00
プログラミングマスコットのドット絵を書いた
プログラム言語のマスコット、ロゴ
あなたはPCにステッカー貼ってますか?
利用言語を愛していますか?
言語のOSSにプルリクエストを送るためなら睡眠時間を削れますか?そんなあなたに送るプログラムマスコット`sです。
きっかけはUTme!で自作Tシャツを作りたいと思っていた時に、プログラマーが目にするマスコットアニマル`sという記事を見たこと。
あと、三連休なのに予定がないからだZE(2019/07/14)
Gopher (Go)
マスコットといえばgopherくん。
ドット絵と寸胴ボディの親和性がGood。
"gopher" by Renée French CC-BY-3.0Duke (java)
Go言語のライバル?でもあるjavaのDukeさん。
マザー2のザコキャラ感がすんごい。
"Duke" by Sun Microsystems BSDrustacean (rust)
rustのrustaceanさん。
前者2人を圧倒的速度で凌駕する高速カニさん。(強い)Moby Dock (Docker)
DockerのMoby Dock親方。
みんなを支える縁の下の力持ち。
android robot (android)
その名の通りアンドロイドのロボットでandroid robotさん。
ドット絵にしても変わった感が無い・・・
"android robot" by google.com CC BY 3.0jenkins logo (jenkins)
怒り顔など、何かと改変されがちなジェンキンスのロゴ。
あなた、名が無きおじさんだったのね・・・
"jenkins logo" by jenkins.io CC BY-SA 3.0Tux (linux)
linuxのTuxさん。
可愛く見えて実は二段腹のおじさんペンギン。
"Tux" by Larry Ewing、Simon Budig、Anja GerwinskiGitLab logo (GitLab)
昔は怖かったGitLabのロゴ。
狐じゃないよ!たぬきだよ!
"GitLab logo" by gitlab.com CC BY-NC-SA 4.0Python logo (Python)
もはやマスコットではなくなってきた、Pythonのロゴマーク。
"python logo" by Python Software Foundation PSF licensepostgreSQL logo (postgreSQL)
一番難しかった。ポスグレのぞうさん。
"PostgreSQL" by postgresql.org PostgreSQL licenseMySQL logo (MySQL)
ポスグレ書くならこっちもね!MySQLのイルカさん。
笑顔がかわいい。
Slack logo (Slack)
みんなの雑談部屋。slackのロゴ。
新デザインにだんだん慣れてきた。
※著作権に関する記載が見つからなかったので、問題あればコメントなどで指摘してください。HTML5 logo (HTML5)
HTML5のロゴ。
筆者は20代半ばなので、5とそれ以前の違いがわかっていない。
"HTML5" by World Wide Web Consortium CC BY 3.0Javascript logo (JavaScript)
HTMLやったならこっちもね。
JavaScriptさん。
※こちらも著作権に関する記載が見当たらなかったです。UBUNTU logo (UBUNTU)
Macが買えず使っていたUBUNTUさん。
WSL2に期待中。
ReactJS logo (ReactJS)
個人的な思い入れ(業務で利用)だけで書いたReactJS。
複雑な構造をドット絵で書くのは無理あるよね。。。
"ReactJS logo" by facebook.com CC BY 4.0最後に
こんな感じのTシャツを着ている人がいたら声をかけてください。きっと私です。
著作権について
この記事に記載している画像はすべてSlackスタンプ、アイコンに流用していただいてかまいません。
ただし、全てのマスコット、ロゴは原案者がいらっしゃいます。
必ず著作権の確認、表示の上ご利用ください。
(octcat-githubは著作権縛りきつくて書いたけど載せれなかった。)
- 投稿日:2019-07-14T07:49:01+09:00
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 finishruntime.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のコールバックは場合によっては使えると思う
でも基本は、ライブラリを使う側が責任もって後始末しなければいけないっぽいハイブリッドは良さそう
- 投稿日:2019-07-14T01:28:26+09:00
GolangでLINEBOTを作った話
はじめに
ある日、とてつもなくやることがなかった(ほんとはあった)のでなにかしようと考えていたら、知り合いに「LINEのBOTとかどう?」と言われたので衝動的に作りました。
動けばいいや精神でつくってます。
はじめに言います。Go言語はずっと学ぼうと思い続けて未だ何も学んでないです。
素人プログラムをお許しください概要
いいAPIがないかと探していたら素晴らしいサービス様を見つけたのでこちらを使用(ありがとうございます)
前準備
Go言語のインストール
Mac
$ brew install goWindows
ダウンロードページから落としましょう
Linux
ディストリビューションごとに
apt-get
とかしてあげてくださいline-bot-sdkのダウンロード
GolangにはLINE公式のsdkが用意されているのでダウンロードします
$ go get github.com/line/line-bot-sdk-go/linebot
LINE公式アカウントの用意
ここからDevelopperアカウントを取得しましょう
早速コードを書く
なんの構想もなく書き始めました。どうも馬鹿です。
とりあえずAPIから受け取ったデータをパースする構造体を作ります。
都道府県別データのレスポンスは
<ekidata version="ekidata.jp pref api 1.0"> <pref> <code>23</code> <name>愛知県</name> </pref> <line> <line_cd>11411</line_cd> <line_name>JR中央本線(名古屋~塩尻)</line_name> </line> <line> <line_cd>11413</line_cd> <line_name>JR飯田線(豊橋~天竜峡)</line_name> </line> ...といった具合なので、対応する構造体は
type Pref struct { Pref struct { Code int `xml:"code"` Name string `xml:"name"` } `xml:"pref"` Lines []struct { Code int `xml:"line_cd"` Name string `xml:"line_name"` } `xml:"line"` }こんな感じで良さそうです
この調子で他の構造体も定義してください。肝となる通信をしてデータを受け取る関数です
func getData(endPoint []byte, code int) ([]byte, error) { url := make([]byte, 0, 10) url = mainURL url = append(url, endPoint...) url = append(url, strconv.Itoa(code)...) url = append(url, ".xml"...) res, err := http.Get(string(url)) if err != nil { return nil, fmt.Errorf("%v", err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } return body, nil }これと言って凝った部分もありませんね
BOTに止まられては困るのでエラーはFatal
ではなく戻り値として呼び出し元に返しています構造体、データ受け取りの関数などをひとまとめにしたものをgithubにアップします
Heroku内だと相対パスでのインポートができないようなのでこうしていますが、もっといいやり方知ってる人がいたら教えてください。。。main.goは公式のサンプルを書き換えました
main.gopackage main import ( "fmt" "log" "net/http" "os" "strconv" "strings" "github.com/line/line-bot-sdk-go/linebot" "github.com/Homarechan/datas" "githuc.com/Homarechan/prefcodes" ) var helpMessage = `コマンド一覧 路線一覧:[都道府県] 駅一覧:[駅番号] 駅情報:[駅番号] 所属路線一覧:[駅番号] 隣接駅:[路線番号] 使い方 例: 路線一覧: 大阪府 駅情報:1130224` func main() { port := os.Getenv("PORT") if port == "" { log.Fatal("$PORT must be set") } bot, err := linebot.New( os.Getenv("CHANNEL_SECRET"), os.Getenv("CHANNEL_TOKEN"), ) if err != nil { log.Fatal(err) } // Setup HTTP Server for receiving requests from LINE platform http.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) { events, err := bot.ParseRequest(req) if err != nil { if err == linebot.ErrInvalidSignature { w.WriteHeader(400) } else { w.WriteHeader(500) } return } for _, event := range events { if event.Type == linebot.EventTypeMessage { switch message := event.Message.(type) { case *linebot.TextMessage: if event.ReplyToken == "00000000000000000000000000000000" { return } if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(parse(message.Text))).Do(); err != nil { log.Print(err) } } } } }) // This is just sample code. // For actual use, you must support HTTPS by using `ListenAndServeTLS`, a reverse proxy or something else. if err := http.ListenAndServe(":"+os.Getenv("PORT"), nil); err != nil { log.Fatal(err) } } func parse(message string) string { if startsWith(message, "路線一覧:") { return fetchText(message[13:len(message)]) } else if startsWith(message, "駅一覧:") { code, err := strconv.Atoi(message[10:len(message)]) if err != nil { return "エラー:\n路線一覧で取得した路線番号を入力してください" } return getLineData(code) } else if startsWith(message, "駅情報:") { code, err := strconv.Atoi(message[10:len(message)]) if err != nil { return "エラー:\n駅一覧で取得した駅番号を入力してください" } return getStationData(code) } else if startsWith(message, "所属路線一覧:") { code, err := strconv.Atoi(message[19:len(message)]) if err != nil { return "エラー:\n駅一覧で取得した駅番号を入力してください" } return getGroupData(code) } else if startsWith(message, "隣接駅:") { code, err := strconv.Atoi(message[10:len(message)]) if err != nil { return "エラー:\n駅一覧で取得した駅番号を入力してください" } return getJoinData(code) } return helpMessage } func startsWith(str string, text string) bool { return strings.HasPrefix(str, text) } func fetchText(message string) string { text := message if len(text)/3 == 2 { if text == "大阪" || text == "京都" { text += "府" } else if text == "東京" { text = "東京都" } else { text += "県" } } else if len(text)/3 == 3 { if text == "神奈川" || text == "和歌山" { text += "県" } } _, ok := prefcodes.NameToCode[text] if !(ok) { return "都道府県名が無効です" } return getPrefData(text) } func getPrefData(pref string) string { linesInterface, err := datas.GetPrefData(prefcodes.NameToCode[pref]) if err != nil { return err.Error() } lines := linesInterface.Lines result := "[" + pref + "]\n" for _, line := range lines { result += "\n" result += strconv.Itoa(line.Code) result += ": " result += line.Name } return result } func getLineData(linecode int) string { stations, err := datas.GetLineData(linecode) if err != nil { return "エラー:\n路線一覧で取得した路線番号を入力してください" } result := "[" + stations.Line.Name + "]\n" for _, station := range stations.Stations { result += "\n" result += strconv.Itoa(station.Code) result += ": " result += station.Name } return result } func getStationData(stationcode int) string { station, err := datas.GetStationData(stationcode) if err != nil { return "エラー:\n駅一覧で取得した駅番号を入力してください" } result := "[" + station.Station.Name + "]\n\n" result += "駅コード: " + strconv.Itoa(station.Station.Code) result += "\n" result += "駅グループコード: " + strconv.Itoa(station.Station.GroupCode) result += "\n" result += "路線: " + station.Station.LineName + "(" + strconv.Itoa(station.Station.LineCode) + ")" result += "\n" result += "都道府県: " + prefcodes.CodeToName[station.Station.PrefCode] result += "\n" result += "緯度: " + fmt.Sprintf("%f", station.Station.Latitude) result += "\n" result += "経度: " + fmt.Sprintf("%f", station.Station.Longtitude) return result } func getGroupData(stationcode int) string { group, err := datas.GetGroupData(stationcode) if err != nil { return "エラー:\n駅一覧で取得した駅番号を入力してください" } result := "[" + group.Station.Name + "]\n" for _, line := range group.GroupStations { result += "\n" result += strconv.Itoa(line.LineCode) result += ": " result += line.LineName result += "\n名称: " result += line.Name } return result } func getJoinData(stationcode int) string { joins, err := datas.GetJoinData(stationcode) if err != nil { return "エラー:\n路線一覧で取得した路線番号を入力してください" } line, err := datas.GetLineData(stationcode) if err != nil { return err.Error() } result := "[" + line.Line.Name + "]\n\n" for _, join := range joins.StationJoins { result += "隣接駅1:" result += join.Name1 result += "(" result += strconv.Itoa(join.Code1) result += ")\n" result += "隣接駅2:" result += join.Name2 result += "(" result += strconv.Itoa(join.Code2) result += ")\n\n" } return result }Herokuの設定
貧乏な学生なのでサーバーを借りるお金はないのでHeroku様を使います
Herokuのアプリを起動した際に読み込まれるProcfileを作ります
$ echo "web: $(basename `pwd`)" > Procfileパッケージ管理にはgovendorを使います
$ go get -u github.com/kardianos/govendor $ govendor init $ govendor fetch +outgitのセットアップを行います
$ git init $ git add . $ git commit -m "first commit"あとはHerokuにデプロイするだけです
$ heroku create $ heroku config:add CHANNEL_SECRET="チャンネルシークレット" $ heroku config:add CHANNEL_TOKEN="チャンネルトークン" $ git push heroku master最後に、作成したアプリのWebhookURLをlinebotの管理画面で登録すれば完成です
完成品
https://github.com/Homarechan/Go-Bot
http://line.naver.jp/ti/p/@989mooot最後に
Haskell書きたい