2020年5月15日

[golang]go getとgo.mod

go getするとき、pkgに落ちることもあれば、srcに落ちることもある。
ルールがよくわからんなー、と思っていたのだが、もしかしてgo.modによるのか?

[golang]selectとchan

golangで、chanの受信をしているコードがあった。
普通に?1行で受信しているのではなく、selectのcaseで受信していたのだ。
しかも複数chan!
受信って、バッファがあるときはどうだったか忘れたけど、ブロッキングになってるんじゃなかったっけ?


そうでもないらしい。

Go言語でチャネルとselect - Qiita
https://qiita.com/najeira/items/71a0bcd079c9066347b4#select

selectだからというわけでもないだろうが、selectシステムコールのように複数の待ち受けができるらしい。

もしselectがなかったら、どうするんだろう? 非同期はgoroutineで対処するものなのだろうから、どうしようもないのかな。。。

2020年5月4日

[golang]手軽そうなkey value storeタイプのDB

golangの勉強をしているが、ちょっとしたデータ保存をしたいと思った。
大げさに言えばデータの永続化だが、プロセスを終了させてもデータが残っているようにしたいだけだ。

 

SQLiteを考えたが、大して検索などしないならkey value storeタイプでもよいだろう。
Cでlmdbを使っていたので、その辺で探してみた。

 

なんとなく手軽そうなので、bboltを使ってみよう。
boltdb/boltというリポジトリがオリジナルだったようだが、そちらはarchiveになっていた。


ちょっとチュートリアルをやって終わろうと思ったが・・・説明が長い!
DBだし、lmdbをベースにしているとなるとマルチプロセスやらマルチスレッドやらgoroutineやらあるので、ちゃんと説明がいるのだろう(読んでないけど)。

 

クロージャーとかいうものを使っているらしい。
他の言語でも聞いたことがある単語だが、まあスルーさせてもらおう。

よくわかってないが、bucketというものを作ってアクセスするようだ。Put/GetがBucketにしかないしね。
lmdbのmdb_dbi_open()で指定するnameのようなものだろうか。

01: package main
02: 
03: import (
04:     "log"
05: 
06:     "go.etcd.io/bbolt"
07: )
08: 
09: func main() {
10:     db, err := bbolt.Open("./db.bbolt", 0666, nil)
11:     if err != nil {
12:         log.Fatalf("%v\n", err)
13:     }
14:     defer db.Close()
15: 
16:     err = db.Update(func(tx *bbolt.Tx) error {
17:         b, err := tx.CreateBucket([]byte("MyBucket"))
18:         if err != nil {
19:             if err == bbolt.ErrBucketExists {
20:                 b = tx.Bucket([]byte("MyBucket"))
21:             } else {
22:                 log.Fatalf("create bucket: %v\n", err)
23:             }
24:         }
25:         err = b.Put([]byte("answer"), []byte("42"))
26:         return err
27:     })
28:     if err != nil {
29:         log.Fatalf("update: %v\n", err)
30:     }
31:     err = db.View(func(tx *bbolt.Tx) error {
32:         b := tx.Bucket([]byte("MyBucket"))
33:         v := b.Get([]byte("answer"))
34:         log.Printf("The answer is: %s\n", v)
35:         return nil
36:     })
37:     if err != nil {
38:         log.Fatalf("view: %v\n", err)
39:     }
40: }

 

まあ、サンプルが動いただけなので、これでいいのかどうかはよくわからん。
追々だ、追々。

追記:
今回の使い方だったら、CreateBucketIfNotExists()の方がよさそうだ。

 

最初、これをWindows10のWSLで動かそうとしたのだが、db.Update()の方は通るもののdb.View()の方で落ちてしまった。DBファイルが存在すると動く。
Linuxの方だと問題なく動くので、これはWSLでlmdbを動かしたときと同じくファイルシステムの問題だろう。
WSL2に期待しております。

[golang]named return values

golangでは戻り値とする変数名に予め名前を決めておくことができる。

Named return values
https://tour.golang.org/basics/7

named returnではなく、named return valuesなので、return valuesがnamedなのだ。

 

01: package main
02: 
03: import "fmt"
04: 
05: func namedReturn(param int) (a int, b string) {
06:     if param == 0 {
07:         a = 10
08:         b = "hello"
09:         return
10:     } else {
11:         return 99, "world"
12:     }
13: }
14: 
15: func main() {
16:     p, q := namedReturn(0)
17:     fmt.Printf("p=%d, q=%s\n", p, q)
18:     p, q = namedReturn(1)
19:     fmt.Printf("p=%d, q=%s\n", p, q)
20: }

かといって、名前を付けていたからといってreturnが空っぽ(naked returnというそうだ)でなければ指定した値を返すこともできる。

この辺は、namedにしたらnamedだけ、みたいなルールの方が良かったんじゃないかと思うが、まあ自分で気をつければいいか。

2020年5月3日

[golang]go.modのreplaceを使ってlocal pathにする

ルールが分かればあきらめが付くはず、というわけで、golangのモジュールを理解しようと努めている。
いつか決定版の記事を書きたいものだが、それまでは試行錯誤が続く・・・。

 

golangで実装する場合、必ずgitなりなんなりを使うとは限らない。
どちらかといえば、使わない場合の方が多いかもしれない。
しかし、golangはおそらくバージョン管理システムを使う前提になっているので、そこで混乱してしまうのだろう(仮説)。

 

というわけで、これだ。

When should I use the replace directive?
https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive

Can I work entirely outside of VCS on my local filesystem?
https://github.com/golang/go/wiki/Modules#can-i-work-entirely-outside-of-vcs-on-my-local-filesystem

Version Control Systemを使ってないローカルファイルシステムでもできますか?というところか。
こういうFAQがあるからVCSを使う前提なのだろうと感じているのだ。Googleはなんでもネット上においてもらう戦略なんだけど抜け道はあるよ、ということなのかしらね。

 

replace自体はそんなに難しいものではなく、左側になっているものを右側とみなしてgoコンパイラというかgoコマンドが処理する、というところだろう。
左側にはimportに書いたものを、右側には置き換えたい先を書く。基本的にネットにあるものを取得する前提なので、ローカルパスを指定する場合は、

  • /で始まるパス(絶対パス)
  • ./で始まるパス(カレントディレクトリからの相対パス)
  • ../で始まるパス(上位ディレクトリからの相対パス)

のいずれかになる。

 


ここまでは、まあいいだろう。

いずれGitHubにアップする予定で、2つのpackageを作っていたとしよう。
github.com/hirokuma/helloとgithub.com/hirokuma/world。
helloはworld側の関数を呼び出して文字列を取得し、それをfmt.Printf()するだけということにする。
world側はwordingとlistというpackageを内部で持っていて、wordingの方はlistから文字列を取得するようにしている。

 

これをGitHubにアップする前にビルドしたい。
そうすると、最終的にこうなった。

 

image

hello/go.mod

01: module github.com/hirokuma/hello
02: 
03: go 1.14
04: 
05: require github.com/hirokuma/world/wording v0.0.0
06: require github.com/hirokuma/world/list v0.0.0
07: 
08: replace github.com/hirokuma/world/wording => ../world/wording
09: replace github.com/hirokuma/world/list => ../world/list
  

 

  • helloからは直接参照していないworld/listについてもgo.modに書かないといけない
  • world/listにもreplaceがいる
  • world/wordingとworld/listにそれぞれgo.modがいる

replaceのwildcard指定については要望にありそうだが、v1.4には入っていないし、今後も不明だ。

 

じゃあ、いっそのことimportの方を相対パスで書けばいいやん、と思ったのだが、それはダメだった。
"non-local package"はエラーらしい。
packageのあるディレクトリにあるファイルはダメだとか、そんな記事があったけど確かめてはいない。

Relative import paths(これの2番目の方)
https://golang.org/cmd/go/#hdr-Relative_import_paths

>To avoid ambiguity, Go programs cannot use relative import paths within a work space.
などといっているので、work space(作業ディレクトリ?)を全然別にしてしまえばよいのかもしれん。

が、なんか面倒なので、gitのsubmoduleにして全部平たくしてしまう方がいいのか。

 

ちなみに、go.modにrequireを書いていない場合、go buildすると勝手に追加された。

01: module github.com/hirokuma/hello
02: 
03: go 1.14
04: 
05: replace (
06:     github.com/hirokuma/world/list => ../world/list
07:     github.com/hirokuma/world/wording => ../world/wording
08: )
09: 
10: require (
11:     github.com/hirokuma/world/list v0.0.0-00010101000000-000000000000 // indirect
12:     github.com/hirokuma/world/wording v0.0.0-00010101000000-000000000000
13: )

勝手に追加しないで、別ファイルにしてくれると非常に助かるのだけどね! せめて更新を許可したときだけ上書きとかにしてほしいんだけどね!

2020年4月27日

VirtualBox 6.1.6にしたらMint Linuxのフルスクリーンが黒い

タイトル通り。

VirtualBoxを6.1.6にアップデートしたのだが、Mint Linuxをフルスクリーンにすると黒い画面になってしまうようになった。
フルスクリーンでも、最初に出てくるログイン画面は大丈夫なのだ。あそこまではVGAだかSVGAだかのサイズになっていて、画面の中央に表示される。
そこでログインすると、真っ黒なのだ。

 

一度フルスクリーンを解除すると表示されるので、またフルスクリーンに戻せば済む。
済むのだが、何度かやっていると面倒になってきた。

じゃあ1つ前のバージョンに戻すかというと、そうするとクリップボードの共有で問題が出てくる。
なので、6.1.2まで戻した。
そうすると、うん、ちゃんと表示されるね。

 

なんかMint Linuxにしてからあれこれ相性が悪いのだ。
もしかしたらcinnamonとかいうのを選択したからかもしれないが、LubuntuかXubuntuに戻そう。
ここは、最近出たばかりの20.04LTSを試すべきか。

2020年4月26日

[golang]"local import "./xxx" in non-local package"はエラーなのか?

こそこそとgolangの勉強を続けている。
別にこそこそしなくてもよいのだが、成果が上がらないので書けるものが無い、というところだ。

 

golangの勉強をしていて一番嫌なのは、importについてだ。
基本的に公開しないプログラムを書くことが多いので(お仕事だからね)、localでgit管理はしていてもリポジトリを外部に公開しないことが多い。
でも、golangはローカルを参照するのは推奨しないとかいってるじゃないか。
なんか、もう、あー、うー、みたいな気持ちになってしまう。

 

そこで、golangのimportについては言語仕様ではない、と思い込むことにしようと考えた。
例えば、importする中に"yoyo"と書いていた場合、そんなものはないのでエラーになる。

package yoyo is not in GOROOT (/usr/lib/go-1.14/src/yoyo)

では、これを"./yoyo"にしてみる。

local import "./yoyo" in non-local package

まあ、実際にないので、これもわかる。
"mkdir yoyo"としても同じ。

では、ここでyoyoディレクトリを作って、その中にhoho.goを作り、"package yoyo"という1行だけのファイルを置く。

local import "./yoyo" in non-local package

うーーーん・・・。
バイナリができていないのでエラーなのだろうけど、ディレクトリつくってpackage指定までしてもimportで文句を言われるというのは、いったいなんなのだろうか?


あれこれやったのだけど、go.modでreplaceしているときはgo get -uじゃない方がよさそう、ということくらいしかわからなかった。

エラー?のようになってたのも、go.modのrequireのところに書いていたせいのような気がしなくもない。が、-u無しでgo getしてもgo.modが更新されてrequireに追加されているようだ。

わからない、わからない。。。

 

今回困っていたのは、github.com/xxxの下に2つディレクトリを作っていたのだ。
ディレクトリAの方は単体で動きつつサブディレクトリA/aでライブラリ的な使い方もできるようにしている。そしてAaは同じ階層にあるA/bをimportしている。
ディレクトリBの方はAのサブディレクトリA/aをimportして使っている。

ビルドしたいのはディレクトリB。
なので、B/go.modではrequireにAaを書いて、replaceでA/aを相対パスで指定していた。
が、そこでgo getすると、

cannot find module providing package github.com/xxx/A/b

のような怒られ方をしてしまう。
そこでreplaceでA/bを相対パス指定するようにするとgo getが通って、B/go.modのrequireにA/bも追加されていた。
ただ、これでもgo get -uするとgitがどうのこうのと怒られてしまった。

 

うん、よくわからんが、go get -uはgo.modよりも強い、ということなのかな。

2020年4月18日

[golang]サブコマンドを使う

gitみたいなコマンドは、サブコマンドであれこれできる。
そんな感じのアプリを作りたい。

 

flagというパッケージがあるのだが、ピンとこなかったのでこちらを使ってみる。

google/subcommands: Go subcommand library.
https://github.com/google/subcommands

標準パッケージではないそうだ。まあいい。

 

Usageをそのまま貼り付けてコンパイル。

引数無しで実行。

$ go run subcmd.go
Usage: subcmd <flags> <subcommand> <subcommand args>

Subcommands:
         commands         list all command names
        flags            describe all known top-level flags
        help             describe subcommands and their syntax
         print            Print args to stdout.

exit status 2

コマンド名がsubcmdなのは、ディレクトリがsubcmdだから、subcmd.goというファイル名だからか。。
main.goにリネーム。

$ go run main.go
Usage: main <flags> <subcommand> <subcommand args>

Subcommands:
        commands         list all command names
        flags            describe all known top-level flags
        help             describe subcommands and their syntax
        print            Print args to stdout.

exit status 2

うーん。

$ go run .
Usage: subcmd <flags> <subcommand> <subcommand args>

Subcommands:
        commands         list all command names
        flags            describe all known top-level flags
        help             describe subcommands and their syntax
        print            Print args to stdout.

exit status 2

うーーーん・・・。
まだこの辺のルールが理解できていない私であった。
まあ、ここはgolangのことだからいいとしよう。

$ ./subcmd commands
help
flags
commands
print

`print`しか実装していないので、残りは自動でやっているのだろう。
表示する順番は、自動が先になっているのかソートされているのか。
`print`を`energy`に変更してみる。

$ go run . commands
help
flags
commands
energy

自動が先になっている。
追加した方がどういう順なのかはわからんな。

 

$ go run . help
Usage: subcmd <flags> <subcommand> <subcommand args>

Subcommands:
        commands         list all command names
        energy           Print args to stdout.
        flags            describe all known top-level flags
         help             describe subcommands and their syntax

ここはソートされるのか。
なお、オプション無しでやcommandsにないパラメータで実行するとhelpと同じ出力+exit status 2だった。

flagsは出力がなかった。
`print`にはあるから、ここのflagsはグローバルなフラグなのだろうか?

先に`print`を見ていこう。

$ go run . print

$ go run . print help
help
$ go run . print --help
print [-capitalize] <some text>:
        Print args to stdout.
     -capitalize
        capitalize output
exit status 2

最後のはエラー扱いなのか。
ということは、こうか。

$ go run . flags print
  -capitalize
        capitalize output

そういうことか。ただ"all known top-level flags"だから、違う使い方があるのかもしれん。

$ go run . print MomonGa -capitalize
MomonGa -capitalize
$ go run . print -capitalize MomonGa
MOMONGA

後ろに付けるとオプションではない扱いになるのか。


では、多少コードを変更してみよう。

 

まず、Register()の順序を変更して、`print`を先頭で行うようにする。

$ go run . commands
print
help
flags
commands

うむ、追加した順番なのだな。
helpも順番通りの方が良い気がするのだが、どうなんだろうね。
でも、CommandGroupというのがあるから、グループにできるのかもしれん。

Register()のところを、こうしてみた。

subcommands.Register(&printCmd{}, "ぷりんと")
subcommands.Register(subcommands.HelpCommand(), "よしお")
subcommands.Register(subcommands.FlagsCommand(), "よしお")
subcommands.Register(subcommands.CommandsCommand(), "ぷりんと")

そうすると、helpがこうなる。

$ go run . help
Usage: subcmd <flags> <subcommand> <subcommand args>

Subcommands for ぷりんと:
        commands         list all command names
        print            Print args to stdout.

Subcommands for よしお:
        flags            describe all known top-level flags
        help             describe subcommands and their syntax

第2引数はグループ名で、グループ内でソートされる、と。
たぶんグループ名でもソートされているのだろう。

2020年4月17日

[golang]ifの1行で実行とエラー判定をしたい

golangのコードを見ていると、ifで実行しつつエラーの判定をしていることがある。

When Should I Use One Liner if...else Statements in Go? - Calhoun.io
https://www.calhoun.io/one-liner-if-statements-with-errors/

ここの最初に出てくるようなやつね。

01: if err := doStuff(); err != nil {
02:   // handle the error here
03: }

見た目から入っていきたいお年頃。こういう書き方はgolangっぽいという気がするのでまねしたい。

 

16進数の文字列が渡されたら処理をして、そうじゃなかったら別の処理、みたいなことをやろうとしていた。
0xで始まる数字であれば16進数と見なそうかと思ったが、そんなに急ぐ処理でも無いのでデコードしてエラーがなければ、という判定までしよう。
20byteなので、0xまで入れたら42文字。

01: if (len(mojimoji) == 42) && (_, err := hex.DecodeString(mojimoji[2:]); err == nil) {
02:     ...
03: } else {
04:     ...
05: }

syntax error: unexpected comma, expecting )

えっ、ダメなの??

ifを2つに分けると、大丈夫。

01: if len(mojimoji) == 42 {
02:     if _, err := hex.DecodeString(mojimoji[2:]); err == nil {
03:         ...
04:     }
05: } else {
06:     ...
07: }

golang以外だったら分けて書くだろうし、やっぱりセミコロンを挟む書き方はちょっと違うのだろう。

 

言語仕様を探し出せなかったのだが、ifにセミコロンがあると、セミコロン前が処理相当になるらしい。
じゃあ、ダメか。

 

先にhex.DecodeString()してしまえば、セミコロンの後ろに条件文を&&で書いてしまえば良いかもしれん。
が、それは嫌だ!
文字長の処理の方が16進数文字列のデコードより処理が軽いのは間違いなかろう。だったら、先に軽い処理で露払いをして、その壁を乗り越えてから本番を迎えるのがよかろう。

 

「急ぐ処理じゃあない」と書いておきながら矛盾している気もするが、譲れない線もあるのだ。
意味があるかどうかは別としてね。。。

2020年4月12日

[linux][xubuntu]Mozcが使えなかったが、何かしたら使えた

タイトルからしてダメダメだな・・・。
そして、内容もその通りなのだ。
すまん。

ただ、記録として残しておきたいだけだ。


事の発端は、VirtualBoxのゲストOSを新しく作ることにしたことだった。

ubuntu系であることは決めていたのだが、それにするかは決めていなかった。
オリジナルもいいし、最近使っていたLubuntuもいい。
別のところでMintを初めて使ってみたのだが、ときどきMintだからうまくいかないと思われるところがあった。もちろん回避方法はあるのだが、今の作業がubuntu環境を前提としているので、ちょっと困る。
ならdockerとかでやりゃいいやん、となるのだが・・・まだうまく使えないのだ。

 

まあ、私の技術的に足りないところは置いておくとして。
今回はXubuntuを使うことにした。Xubuntu 18.04.4。
初めてUbuntu系でオリジナル以外を使ったのが、Xubuntuだったのだ。

そして、ようやく本題。
Xutuntuをインストールして、環境設定をした。
私の場合、全体的にはen_US.UTF8にして、日本語の表示と入力だけはできるようにしている。
英語が苦手なので、ソースのコメントはしばしば日本語で書いているのだ。

日本語を追加してMozcを有効に使用としたのだが、input methodに何も出てこない。
そう、Mozcだけでなく、他の項目も出てこないのだ。
そういえばLubuntuのときもそういうことがあった気がする。

あれこれいじったのだが、結局どれが有効だったかわからないまま、いつの間にかinput methodにリストが表示されるようになっていた。
これはたぶん、Mozcや日本語とは関係なく、リストを更新するところがうまくいかないままだったりとか、そういうものなのかもしれない。

 

ただ、最初に書いたように、何かしたら直った、というレベルなのだ。
今回はfcitxのmozc(という表現であってるのか?)を使っているのだが、im-config, fcitx, fcitx-configなどを最後の方で使った気がする。

さっぱりわからんのだが、input methodに候補が出てこないとダメということはわかった。