- 投稿日:2020-02-15T23:25:53+09:00
【Go】マップサンプルメモ
サンプル
package main import ( "fmt" ) func main() { mapTest := make(map[string]int) mapTest["a"] = 1 mapTest["b"] = 2 fmt.Println(mapTest["a"]) // 1 fmt.Println(mapTest) // map[a:1 b:2] fmt.Println(mapTest["c"]) // 0 -- > 存在しないキーのときは値の型のゼロ値が返る for k, v := range mapTest { fmt.Println(k, v) // a 1 } // 存在チェック // マップ内に指定のキーに対する値が存在しているかは、戻り値を2つ受けて、2番目の戻り値で判断することができる // https://blog.y-yuki.net/entry/2017/04/26/000000 _, ok := mapTest["a"] fmt.Println(ok) // true _, ok = mapTest["c"] fmt.Println(ok) // false // 削除 delete(mapTest, "a") fmt.Println(mapTest) // [b:2 // 代入 mapTest["d"] = 4 fmt.Println(mapTest) // map[b:2 d:4] // 初期化 mapTest2 := map[string]int{ "A": 1, "B": 2} fmt.Println(mapTest2) // map[A:1 B:2] }参考
入門Goプログラミングposted with amazlet at 20.02.15Nathan Youngman Roger Peppé
翔泳社
売り上げランキング: 116,400
- 投稿日:2020-02-15T23:19:11+09:00
Go + Fyne で GUI アプリケーション
Go の GUI ライブラリである Fyne について雑多にあれこれ紹介します.
Fyne とは
Fyne は Go で GUI アプリケーションを作るためのライブラリです.
シンプルでわかりやすい, 簡単に綺麗なデザインが作れる, モバイル等も含めたクロスプラットフォームといった点を売りとしています.
- 公式サイト
- GitHub リポジトリ
基本
インストール 〜 Hello world までは他の方が書いたいくつかの記事があるのでそちらを参照してください.
https://qiita.com/KanikoroRerere/items/e2dcc44e625849526015
https://qiita.com/toromaru/items/58a3e93e67394fa1bb4f基本的なウィジェットの扱い方等については, まずは以下が参考になると思います.
Fyne demo app
https://github.com/fyne-io/fyne/tree/master/cmd/fyne_demo
Fyne のメインリポジトリに含まれるデモアプリケーションです.
このデモをベースに各種ウィジェットの使い方, 動作をコードと合わせながら確認するのがまずは分かりやすいと思います.
examples
https://github.com/fyne-io/examples
Fyne によって実装された簡単なアプリケーションのサンプル集です.
A Tour of Fyne
A Tour of Fyne というものも存在します.
現状ではそれほど内容が充実しているわけではない && ブラウザ上で動作確認するような機能はないため, それほど参考にはならないかもしれません...
雑多な話題
実際に少し Fyne を触ってみて, 気になったところやハマったポイントなどをあれこれ記載します.
ユニットテスト
test
パッケージに, いくつかのテストのための API が用意されています.
これらを利用することで, (ある程度は) GUI の操作等もテストすることが可能です.https://godoc.org/fyne.io/fyne/test
例
起動するとメインウインドウが開き, "Counter" をクリックすることでカウンターのウインドウが開きます.
カウンターでは, ボタンをクリックするたびにカウント表示をインクリメントします.
コードは以下のような感じです.
(全体のコードは lusingander/fyne-test-example にあります)main.go// メインウインドウの表示, 起動 func main() { mainApp := app.New() w := mainApp.NewWindow("Sample") w.Resize(fyne.NewSize(200, 30)) w.SetContent( fyne.NewContainerWithLayout( layout.NewVBoxLayout(), widget.NewButton("Counter", func() { newCounterWindow(mainApp).Show() }), ), ) w.ShowAndRun() }counter.go// カウンターを構成するウィジェット, 現在のカウントを保持 type counter struct { *widget.Label *widget.Button count int } func newCounter() *counter { c := &counter{} c.Label = widget.NewLabel(fmt.Sprintf("Count: %d", c.count)) c.Button = widget.NewButton("Click!", c.countUp) return c } func (c *counter) countUp() { c.count++ c.Label.SetText(fmt.Sprintf("Count: %d", c.count)) } // カウンターを描画するウインドウ type counterWindow struct { fyne.Window *counter } func newCounterWindow(app fyne.App) *counterWindow { counter := newCounter() w := app.NewWindow("Counter") w.Resize(fyne.NewSize(200, 30)) w.SetContent( fyne.NewContainerWithLayout( layout.NewVBoxLayout(), counter.Button, counter.Label, ), ) return &counterWindow{w, counter} }このカウンターに対して, 例えば以下のようなテストを実装することができます.
counter_test.go// ボタンをクリックしてカウントがインクリメントされることを確認 func TestCounter(t *testing.T) { c := newCounter() // ボタンに対するクリック操作 test.Tap(c.Button) test.Tap(c.Button) test.Tap(c.Button) // 三回ボタンをクリックしたので現在のカウントは 3 assert.Equal(t, 3, c.count) } // ウインドウ初期化時にカウンターが初期化され設定されることを確認 func TestNewCounterWindow(t *testing.T) { // test.NewApp() で画面描画によらないダミーの fyne.App を生成 w := newCounterWindow(test.NewApp()) // fyne.Window と counter が生成されている assert.NotNil(t, w.Window) assert.NotNil(t, w.counter) }上述の
test.NewApp()
と同じように,Window
やCanvas
をダミーとして生成する API も存在します.リソースファイルの取り扱い
アイコン/画像
GUI アプリケーションでは, ボタンのアイコンとして画像を使用したい, ということがあります.
アプリケーションを配布したい場合, 当然画像ファイルなどのリソースファイルもまとめてシングルバイナリとしたいと思います.Fyne ではこれを行うための仕組みや便利なコマンドが提供されています.
fyne コマンド
fyne に関するいくつかの操作を実行できる
fyne
コマンドが提供されています.https://github.com/fyne-io/fyne/tree/master/cmd/fyne
go get でインストールします.
$ go get fyne.io/fyne/cmd/fynebundle
fyne bundle
コマンドを利用することで, 画像等のリソースファイルを Fyne で扱えるデータ(バイト列生データを持った構造体)に変換することができます.$ fyne bundle foo.svg > bundle.go
bundle.go
の中身は以下のようなファイルになっています.bundle.gopackage main import "fyne.io/fyne" var fooSvg = &fyne.StaticResource{ StaticName: "foo.svg", StaticContent: []byte{ ... // 生データの数値列使ってみる
あとは必要な箇所(
fyne.Resource
型を要求する箇所)にこのStaticResource
を渡してやればよいだけです.たとえばボタンであれば以下のようになります.
widget.NewButtonWithIcon("button", fooSvg, func() { /* do something */ })ボタンのアイコンとして SVG ファイルを設定する, というような場合, テーマに合わせて適切な色で描画したいということがあります.
theme.NewThemedResource
を利用することで, テーマカラーに合わせていい感じの描画を行ってくれます.widget.NewButtonWithIcon("button", theme.NewThemedResource(fooSvg, nil), func() { /* do something */ })具体的な例については, 記事の最後に記載しているサンプルのアプリケーションなどを参考にしてください.
フォント
Fyne は現在デフォルトでは日本語での表示ができません
以下のような簡単なサンプルアプリケーションで確認してみます.
ラベル, ボタンがあり, ボタンを押すとダイアログが表示されます.main.gofunc main() { a := app.New() a.Settings().SetTheme(theme.LightTheme()) w := a.NewWindow("font") w.Resize(fyne.NewSize(300, 200)) w.SetContent( fyne.NewContainerWithLayout( layout.NewVBoxLayout(), layout.NewSpacer(), widget.NewLabel("こんにちは、フィーネ"), widget.NewLabel("これは日本語のラベルです"), widget.NewButton("これはボタンです", func() { dialog.ShowInformation("確認", "これはダイアログです", w) }), layout.NewSpacer(), ), ) w.ShowAndRun() }
go run main.go
してみると以下のようなことになります.
悲しすぎます. 全く OK じゃありません.こちらについては Issue もよく上がっているのですが, いずれ公式に対応したいと思ってるよ, というステータスのようです.
よって日本語を扱いたい場合は一手間必要になります.
現状, 以下の二つの対応が公式に与えられています.
- 環境変数
FYNE_FONT
の利用- カスタムテーマを用意する
FYNE_FONT
の利用Fyne の組み込みテーマを利用する場合, 環境変数
FYNE_FONT
にフォントファイルが指定されている場合にはそれをフォントとして利用することができます.先ほどと同じコードで,
FYNE_FONT
を指定した上で実行してみます.(サンプルのフォントとして M+ FONTS を利用しています. http://mplus-fonts.osdn.jp/)
$ FYNE_FONT=mplus-1c-regular.ttf go run main.go
今度は綺麗に表示されました!
とはいえ, 自分の手元でちょっと試すだけならまだしも, アプリケーションを配布したいと思うとちょっとこのアプローチではいろいろと問題があります.カスタムテーマの設定
Fyne は組み込みで Dark/Light テーマを用意しており, これを利用するだけで綺麗なアプリケーションが作れるよ, というのを一つのウリとしています.
しかし, テーマは自分で設定して利用することももちろん可能です. このテーマで設定できる項目の中にフォントが含まれているため, 独自のテーマを定義することで任意のフォントを利用することが可能になります.テーマは以下のように Theme インターフェースを実装することで利用可能となります.
theme.gotype myTheme struct{} func (myTheme) TextFont() fyne.Resource { return resourceMplus1cRegularTtf } // フォントを設定 // その他の様々な設定 func (myTheme) BackgroundColor() color.Color { return theme.LightTheme().BackgroundColor() } func (myTheme) ButtonColor() color.Color { return theme.LightTheme().ButtonColor() } func (myTheme) DisabledButtonColor() color.Color { return theme.LightTheme().DisabledButtonColor() } // ...このうち,
TextFont
やTextBoldFont
などを適切に実装することでフォントの設定が可能です.
アイコンの場合と同様に,fyne bundle
コマンドによってフォントファイルをバンドルし, それを利用する, ということになります.$ fyne bundle mplus-1c-regular.ttf > bundle.go
theme.go
,bundle.go
が用意できたので, 先ほどmain.go
で指定していたa.Settings().SetTheme(theme.LightTheme())
をa.Settings().SetTheme(&myTheme{})
に修正した上で実行してみます.$ go run *.go
この画像ではダイアログのタイトルがまだ正しく表示されていないですが, これはダイアログが Bold フォントを使用するようになっているためです.
前述の例では Regular のみ指定設定しているためこのようになっています.コード全体は lusingander/fyne-font-example にあります.
キーボード入力/ショートカット
fyne.Canvas
に以下の関数が定義されています.
SetOnTypedRune(func(rune))
SetOnTypedKey(func(*KeyEvent))
AddShortcut(shortcut Shortcut, handler func(shortcut Shortcut))
なので, ウインドウに対してキーボード操作を与えたい場合は以下のような記述をすることになります.
func main() { w := app.New().NewWindow("title") w.Canvas().SetOnTypedKey(v.handleKeys) w.Canvas().SetOnTypedRune(v.handleRune) w.ShowAndRun() } func handleKeys(e *fyne.KeyEvent) { switch e.Name { case fyne.KeyUp: // do something case fyne.KeyDown: // do something // ... } } func handleRune(r rune) { switch r { case '+': // do something case '-': // do something // ... } }また, ショートカットキーを割り当てたい場合は以下のようになります.
func main() { w := app.New().NewWindow("title") // Ctrl+O のショートカットキーを定義 w.Canvas().AddShortcut( &desktop.CustomShortcut{ KeyName: fyne.KeyO, Modifier: desktop.ControlModifier, }, func(s fyne.Shortcut) { // do something }, ) w.ShowAndRun() }コードの通り, 例えば
Ctrl+O
のようなショートカットキーを定義したい場合,desktop
パケージを利用する必要があります.Fyne はモバイルも含めたクロスプラットフォームの GUI ライブラリとして作られているため, デスクトップアプリケーション特有の処理はこのように分離されています.
fyne
パッケージにはデフォルトのショートカット定義として以下が存在しますが, これらはそれぞれモバイル環境でも対応する動作が定義されているものになります.
- ShortcutPaste
- ShortcutCopy
- ShortcutCut
- ShortcutSelectAll
https://github.com/fyne-io/fyne/blob/master/shortcut.go
これらを
AddShortcut
の第一引数に与えると, 各環境で対応する動作を行った際に第二引数のコールバックが呼ばれることになります.遊んでみた
実際に簡単なアプリケーションを作って遊んでみたので紹介します.
go-gif-viewer
GIF アニメーションを再生できる単純なビューアです.
この記事で記載した, リソースファイルの組み込みやキーボードショートカットの実装などを含んでいます.まとめ
まだまだ発展途上のライブラリであり, 不足している機能等も多いですが, 興味があれば是非触ってみてください.
- 投稿日:2020-02-15T23:03:51+09:00
python初心者がIT企業にインターンしてみた[3日目 雲行きが・・・]
昨日疲れすぎて投稿できんやったから今日投稿する
衝撃の事実
今日も元気よく出勤。今日の昼飯は俺の好きなカレーパンだ。なんて呟いているおじさんを横目に、コーヒーを購入(俺かと思ったか)。
それはさておき、いつも通り朝の打ち合わせがあったが、そこでまさかの事実を聞かされる。「来週までに企画提案書を提出してね。あとその企画をクライアントにプレゼンしてもらうからよろしく。」
What a fork!!!(good placeより引用)AIをつかったチャットボット 開発?
打ち合わせで企画をプレゼンすることになったのだが、その内容はAIを使ったチャットボット。それを聞かされて一日が始まったわけだが、何もわからないためまずチャットボットサービスの比較表を作った。会社にデータがあるため載せられないけど、軽く紹介しよう。
チャットボットとは?
チャットボットは人間が入力するテキストや音声に対して、自動的に回答を行うことで、これまで人間が対応していた「お問い合わせ対応」「注文対応」などの作業を代行することができる。(まるまるコピー)
https://ferret-plus.com/8998 チャットボット とはチャットボット の中にも色々タイプが分かれていて、
選択肢タイプ
ログタイプ
ハッシュタイプ
Elizaタイプここの説明は先にはったリンクを参照してくれ。
クライアントの抱える問題は社内のお問い合わせ問題である。例えば、「エクセルの使い方わかりません」「なぜこの会社のトイレットペーパーはシングルなんですか?」などのしょうもない質問をいちいち管理部が聞いてられないという問題だ。そこで、チャットボット ですべて解決してもらいたいとのこと。いつかは人件費も削減したいしうちの会社もそろそろAIを導入じゃ的な?
しかし、この問題インターン生に解決させる?聞くところによるとIT企業ではあるものの、まだAIに手を出した人はいないらしく、どうせできないんだったらインターン生に投げてみようぜ的なやーつーである。
これで来週までに企画書だと?笑かしよるわ、俺にできないことなどナーーーイ(完全にバカ)。
なんやかんやで承諾した俺は今日土曜日に、プログラミングメンターサイトに面談を予約して相談してみた。scalaとpython またはGoとpython
私が予約したのは、チャットボット(AI)の開発経験のある方であった。なにいっているかわからなかったところが多いので、メモったキーワードだけ書いておく。
フルスクラッチ
gcp dialog flow
Go
scala
python
UX的
全言語検索
全文章をベクトル化
モデルに学習させる
テキストをグルーピングてな感じである。
まあ明日はこのキーワードをつかってめちゃくちゃ調べて勉強しようかなと思う。
終わりに
毎日1時間の英会話(友達のネパール人と無料で)して30分メンターさんと面談して、いろんな人と会うために動き回っていると睡眠は気絶するようにねむることができる。毎日疲れるが、人生で一番充実していてとても楽しい。このように忙しくなれたのは、人と出会いまくって得ることができた機会である。皆さんにも人と出会いまくっていろんな体験ができるよう願っております。
2日目の乱雑でごめんな
- 投稿日:2020-02-15T20:45:37+09:00
[geth改造]トランザクションが送られてきた時だけブロックを生成する
記事の内容
自分で勉強している時に空ブロックが大量に作られるのが煩わしかったので、トランザクションが送られてきた時だけブロックを生成するように改造しました。
環境
geth:v1.9.8-stable
golang:1.13.3gethのclone
今回使用したgethをcloneしてきます。
※当記事執筆時点ではmasterからダウンロードすると「unstable」のバージョンが落ちてきたので、安定版を指定して落としています。git clone -b v1.9.8 https://github.com/ethereum/go-ethereum.gitコードの修正(miner/worker.go)
該当のコードを修正します。
修正ソースは「miner/worker.go」の547行目から始まる「resultLoop」関数です。
まずは修正前のコードです。miner/worker.go// resultLoop is a standalone goroutine to handle sealing result submitting // and flush relative data to the database. func (w *worker) resultLoop() { for { select { case block := <-w.resultCh: // Short circuit when receiving empty result. if block == nil { continue } // Short circuit when receiving duplicate result caused by resubmitting. if w.chain.HasBlock(block.Hash(), block.NumberU64()) { continue } var ( sealhash = w.engine.SealHash(block.Header()) hash = block.Hash() ) w.pendingMu.RLock() task, exist := w.pendingTasks[sealhash] w.pendingMu.RUnlock() if !exist { log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) continue } // Different block could share same sealhash, deep copy here to prevent write-write conflict. var ( receipts = make([]*types.Receipt, len(task.receipts)) logs []*types.Log ) for i, receipt := range task.receipts { // add block location fields receipt.BlockHash = hash receipt.BlockNumber = block.Number() receipt.TransactionIndex = uint(i) receipts[i] = new(types.Receipt) *receipts[i] = *receipt // Update the block hash in all logs since it is now available and not when the // receipt/log of individual transactions were created. for _, log := range receipt.Logs { log.BlockHash = hash } logs = append(logs, receipt.Logs...) } // Commit block and state to database. stat, err := w.chain.WriteBlockWithState(block, receipts, task.state) if err != nil { log.Error("Failed writing block to chain", "err", err) continue } log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent{Block: block}) var events []interface{} switch stat { case core.CanonStatTy: events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) events = append(events, core.ChainHeadEvent{Block: block}) case core.SideStatTy: events = append(events, core.ChainSideEvent{Block: block}) } w.chain.PostChainEvents(events, logs) // Insert the block into the set of pending ones to resultLoop for confirmations w.unconfirmed.Insert(block.NumberU64(), block.Hash()) case <-w.exitCh: return } } }若干自分の理解に自信が無いところはありますが、理解した範囲で解説
この関数は「w.exitCh」となるまで、無限ループをしている訳ですが、この「w」が「miner.start()」を行うと動くゴルーチンの様です。
そのため、「w.exitCh」は「miner.stop()」が実行されるまでは動きません。ブロックの生成処理自体は「w.chain.WriteBlockWithState(block, receipts, task.state)」の処理を追っていった先で行われています。
そのため、現在のgethの処理ではトランザクションの有無によるブロック生成処理の要否判定を行っていません。つまり、「w.chain.WriteBlockWithState(block, receipts, task.state)」この処理をトランザクションが無い場合は通らないように改修します。
改修箇所はこの処理の前後もブロック生成の前処理、後処理が実装されていますので、前後の処理もトランザクションが無い場合は通らないようにします。miner/worker.go// resultLoop is a standalone goroutine to handle sealing result submitting // and flush relative data to the database. func (w *worker) resultLoop() { for { select { case block := <-w.resultCh: // Short circuit when receiving empty result. if block == nil { continue } // Short circuit when receiving duplicate result caused by resubmitting. if w.chain.HasBlock(block.Hash(), block.NumberU64()) { continue } var ( sealhash = w.engine.SealHash(block.Header()) hash = block.Hash() ) w.pendingMu.RLock() task, exist := w.pendingTasks[sealhash] w.pendingMu.RUnlock() if !exist { log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) continue } // Different block could share same sealhash, deep copy here to prevent write-write conflict. var ( receipts = make([]*types.Receipt, len(task.receipts)) logs []*types.Log ) if len(task.receipts) != 0 { for i, receipt := range task.receipts { // add block location fields receipt.BlockHash = hash receipt.BlockNumber = block.Number() receipt.TransactionIndex = uint(i) receipts[i] = new(types.Receipt) *receipts[i] = *receipt // Update the block hash in all logs since it is now available and not when the // receipt/log of individual transactions were created. for _, log := range receipt.Logs { log.BlockHash = hash } logs = append(logs, receipt.Logs...) } // Commit block and state to database. stat, err := w.chain.WriteBlockWithState(block, receipts, task.state) if err != nil { log.Error("Failed writing block to chain", "err", err) continue } log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent{Block: block}) var events []interface{} switch stat { case core.CanonStatTy: events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) events = append(events, core.ChainHeadEvent{Block: block}) case core.SideStatTy: events = append(events, core.ChainSideEvent{Block: block}) } w.chain.PostChainEvents(events, logs) // Insert the block into the set of pending ones to resultLoop for confirmations w.unconfirmed.Insert(block.NumberU64(), block.Hash()) } case <-w.exitCh: return } } }修正箇所は「for i, receipt := range task.receipts {」の前に「if len(task.receipts) != 0 {」を追加しています。
この条件判定で処理待ちトランザクションが0ではない場合という条件になります。動作検証
まずはビルドです。
make geth次にgethを起動します。
geth --networkid 1111 --nodiscover --datadir "/home/eth_private_net" console 2>> /home/eth_private_net/geth_err.log※別のコンソールでログを監視しておきます。
ここからは「Geth JavaScript console」のコマンドです。
> miner.start() nullログ監視コンソールでは以下のログ以降は何も出力されません。
普通ならブロック生成のログがたくさん出力されます。INFO [02-15|20:27:04.047] Updated mining threads threads=1 INFO [02-15|20:27:04.047] Transaction pool price threshold updated price=1000000000トランザクションを送信してみます。
> personal.unlockAccount(eth.accounts[0]) Unlock account 0xb17853e0a116c46875132625e564d55bd85a433e Password: true > eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[1], value:web3.toWei("1", "ether")}) "0x1a46b274639a6d2d4a36a562313cc3cafb9f1ba80591aaf9b196090370855221" > eth.getTransaction("0x1a46b274639a6d2d4a36a562313cc3cafb9f1ba80591aaf9b196090370855221") { blockHash: "0x5f7caf6cd09b0020deb5a16f501d442f0f47c293685d5ed6908935cd32fca62d", blockNumber: 280, from: "0xb17853e0a116c46875132625e564d55bd85a433e", gas: 21000, gasPrice: 1000000000, hash: "0x1a46b274639a6d2d4a36a562313cc3cafb9f1ba80591aaf9b196090370855221", input: "0x", nonce: 3, r: "0x6f1e5ba7be3ccb695b2a33ac74812609cb489f5d3910266badeb192d1ef24042", s: "0x2913b8a2151b05835a879f1b74df2b0c3279d70b83c58146fdadd9b58f2e811", to: "0xee72c44a047856be6d4a095b0920de53e030f58d", transactionIndex: 0, v: "0x8d2", value: 1000000000000000000 }トランザクションの送信が成功し、ブロックに取り込まれるところまで確認できました。
ログも見てみます。INFO [02-15|20:30:49.713] Setting new local account address=0xB17853E0a116C46875132625E564D55bD85a433E INFO [02-15|20:30:49.713] Submitted transaction fullhash=0x1a46b274639a6d2d4a36a562313cc3cafb9f1ba80591aaf9b196090370855221 recipient=0xee72C44a047856BE6d4A095b0920De53e030f58D INFO [02-15|20:30:52.117] Commit new mining work number=280 sealhash=e74429…eaec65 uncles=0 txs=1 gas=21000 fees=2.1e-05 elapsed=307.299µs INFO [02-15|20:31:05.219] Successfully sealed new block number=280 sealhash=e74429…eaec65 hash=5f7caf…fca62d elapsed=13.102s INFO [02-15|20:31:05.220] ? mined potential block number=280 hash=5f7caf…fca62dトランザクションの送信ログとブロックの生成ログのみ出力され、このログ以降のログは出力されていません。
凄く簡単にですが、動作確認は完了です。どうでもいいですが、コンソールだと「・・・」って出てるところをそのまま貼り付けると「?」になるんですね・・。
感想
今回はトランザクションの有無でブロックを生成するかどうか判定を加えました。
トランザクションがある場合に一度のブロック生成で処理するトランザクション数などもこの辺りの処理に追加してあげるとパフォーマンスが安定したりするのかもなんて思いました。
- 投稿日:2020-02-15T18:35:18+09:00
Goroutineを雑多にまとめる
Goroutineの文法を自分用メモとしてまとめる。
sync.WaitGroup
複数のスレッドで並列処理を行う際、その全ての完了を確認するために使用できる。WaitGroupのカウンタを用いる。(Javaのjava.util.concurrent.CountDownLatchによく似ているらしい)
// WaitGroupの値を作る wg := &sync.WaitGroup{} // wgのカウントをインクリメント wg.Add(1) go func(wg *sync.WaitGroup) { // wgのカウントをデクリメント defer wg.Done() 処理 } // カウントが0なら次に進める wg.Wait() 処理[注意]
Addされるカウントの合計 = Done()の合計個数
である必要ありチャネル
- 中身の個数が決まってるのが
bufferred channel
、決まっていないのがunbufferred channel
。// チャネルの作成 ch = make(chan int) // チャネルへ値を突っ込む ch <- x // チャネルから値を取り出す y := <- ch // チャネルの中身をループ // もし他にプログラムが入っていない、つまり、このchに新しい値が入る可能性がないならばループ前にcloseする必要あり close(ch) for c := range ch { 処理 }
fan out
/fan in
// 順にfan out / fan in def(first <-chan int, second chan<- int){ 処理 }
- { 無限for, select文 }からの抜け方
Outerloop: for { select { case <-ch: fmt.Println("Out!") break OuterLoop default: 処理 } } }sync.mutex
複数のgoroutineから一つの変数を書き換えたいときに使う。
// お目当ての変数とMutexを含む構造体を定義する type Counter struct{ v map[string]int mux sync.Mutex } // それを使う func (c *Counter) Inc(key string) int { c.mux.Lock() defer c.mux.Unlock() return c.v[key]++ }参考
- 投稿日:2020-02-15T15:47:53+09:00
Goの文法を雑多にまとめる
Golangの文法を自分用メモとしてまとめる。
雑多なメモ
# ライブラリのdocの確認 $ godoc fmt Println $ godoc os/user Current # コマンドラインでフォーマット形成 $ gofmt -w file.go # ドキュメントをローカルポートから見る $ godoc -http=:6060// %ホニャララのPrintf (Printlnだとダメ) fmt.Printf("%T", var) // 変数の型 fmt.Printf("%v", var) // value in default format fmt.Printf("%d", var) // 10進数で表示 // intは環境によって32bitか64bit,指定したい場合にはint32, int64を使う。 // 文字列からn番目の要素を取り出す fmt.Println("Hello"[0]) // 72(ASCIIコード) fmt.Println(string("Hello"[0])) // "H"(stringに戻す) // エラーを生じうる型変換 i, err := strconv.AtoI("15") // バイト配列 b := []byte{72, 73} // [72 73] c := []byte("HI") // [72 73] string(b) // "HI" // 可変長引数を関数に渡す func foo(params ...int){処理} // paramsにはintのスライスが入る // スライスを展開し各を引数として関数に放る foo([]int{1, 2, 3}...) // 標準パッケージのtesting $ go test ./...クロージャ(関数閉包)
closureの定義のちゃんとした説明には踏み込まず、具体例からやんわりまとめる。
func incrementGenerator() (func() init) { x := 0 return func() int { x++ return x } } func main() { counter := incrementGenerator() fmt.Println(counter()) fmt.Println(counter()) }以上の例だと、counterという変数には無名関数が保存されており、この関数内から、この関数外で定義された変数xを参照している。このため、このxの値は保持され、二度目にcounter()を呼び出した際には、x=1が入っている。
logライブラリ
log.Println("error message") log.Fatalln("error message") // プログラム終了するログをファイルに書き出すようにsettingをするには以下。
func LoggingSettings(logFile string) { logfile, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) //multiWriterは複数のio.Writerを受け取り、すべてに同時に書き込み対象に設定 multiLogFile := io.MultiWriter(os.Stdout, logfile) log.SetFlags(log.LDate | log.Ltime | log.Llongfile) log.SetOutput(miltiLogFile) } func main() { LoggingSettings("test.log") }panicとrecover
golangでは使用が推奨されておらず、エラーハンドリングを使うべし。deferで指定する関数に入れることで生じたpanicをキャッチして処理できる。
// panicのキャッチかつプリント s := recover() fmt.Println("OK?")ポインタ周り
// *int型を返す [ポインタ!!] new(int) // ポインタではなくスライス型を返す make([]int, 0)
初期化
- スライスやマップは
[]int{}
よりmake([]int, 0)
で初期化が推奨される。- 構造体は
new(MyStruct)
より&MyStruct{}
で初期化されることが多い。
(構造体のポインタ).(属性値) = (*構造体のポインタ=実態).(属性値)
が成立、つまり、// 構造体の初期化かつそのポインタの取得 s := &MyStruct{初期化} // どっちでもいい s.field1 = "field1" *s.field2 = "field2"構造体周り
- 構造体のコンストラクタのプラクティス
func New(x, y int) *Mystruct{ return MyStruct{x, y} }
- 構造体の破壊的/非破壊的メソッド
// 破壊的メソッドの定義 func (s *MyStruct) overwrite { s.var = "new value" } // 非破壊的メソッドの定義 func (s MyStruct) overwrite { s.var = "new value" }
- golangの構造体は継承を
embedding(実現)
によって実現する- non-struct型のメソッドを拡張
// カスタムの新しい型を作る type MyInt int func (i MyInt) newMethod {処理}Type Assertionとswitch-assert文
- Type Assertion
インターフェイスから他の型へはtype assertion
、ある型から他の型へはキャスト
、,type conversion
と呼ぶ。// interface{}型からint型へ var a interface{} = 5 b := a.(int) // エラー時にはpanicが生じる
- switch-assert文
switch t := i.(type) { case int: 処理 case string 処理 default: 処理 } }
- 投稿日:2020-02-15T15:37:10+09:00
seek難しい
こんなコードを書くとzipファイルがおかしいとなったのでメモ
target, err := ioutil.TempFile("", "zip") if err != nil { sentry.CaptureException(err) log.Fatalln(err) } w := zip.NewWriter(target) defer w.Close() for _, client := range clients { xlsx, err := run(client.ClientID) if err != nil { continue } defer xlsx.Close() f, err := w.Create(fmt.Sprintf("%d_item_error.xlsx", client.ClientID)) if err != nil { sentry.CaptureException(err) log.Fatalln(err) } if _, err := xlsx.Seek(0, io.SeekStart); err != nil { sentry.CaptureException(err) log.Fatalln(err) } if _, err := io.Copy(f, xlsx); err != nil { sentry.CaptureException(err) log.Fatalln(err) } } if _, err := target.Seek(0, io.SeekStart); err != nil { sentry.CaptureException(err) log.Fatalln(err) } if _, err := internal.Upload(target, "archive.zip", "application/zip"); err != nil { sentry.CaptureException(err) log.Fatalln(err) }何がやりたいかというと、xlsxファイルをクライアントで作って、それをzipにしてs3とかにアップロードしたいコード
これだと、zipが壊れてアップロードされる。
もしかしてFlashされてないのかなと思って
Seekするまに
w.Flashにしても同じ状況に
よくなかった??ので
defer w.Close()しているところみたいで、
Seekをするまにw.Close()を呼ぶべきみたいでした。target, err := ioutil.TempFile("", "zip") if err != nil { sentry.CaptureException(err) log.Print(err) goto exits } w := zip.NewWriter(target) for _, client := range clients { xlsx, err := run(client.ClientID) if err != nil { continue } defer xlsx.Close() f, err := w.Create(fmt.Sprintf("%d_item_error.xlsx", client.ClientID)) if err != nil { sentry.CaptureException(err) log.Print(err) goto exits } if _, err := xlsx.Seek(0, io.SeekStart); err != nil { sentry.CaptureException(err) log.Print(err) goto exits } if _, err := io.Copy(f, xlsx); err != nil { sentry.CaptureException(err) log.Print(err) goto exits } } w.Close() if _, err := target.Seek(0, io.SeekStart); err != nil { sentry.CaptureException(err) log.Print(err) goto exits } if _, err := internal.Upload(target, "archive.zip", "application/zip"); err != nil { sentry.CaptureException(err) log.Print(err) goto exits }とりあえず、deferしてCloseすればいいではなく理解しないとなという。
goto exitsもlog.Fatalでos.Exitになってdeferを呼ばれなくなるので
こうしているどうするのがいいのか。。Flashは非推奨となっているけど、Flashしても壊れるのもよくわからない感じで。
- 投稿日:2020-02-15T11:54:00+09:00
package main
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write("森っている")
})log.Faltal(http.ListenAnd(":8080", nil))}
- 投稿日:2020-02-15T05:55:42+09:00
Go(golang)/ginのAPIでnullableなjsonを返す
状況
- 当たり前の機能だと思うかもしれないが少しハマった。
- golangの仕様で変数にnullを代入できない。例えば
int
型であれば初期化した時点で0の値が初期値として設定される。var n int println(n) // 実行結果 0
- これでどういう問題が発生するかというと構造体でも初期化した時点で勝手に0が入ってしまいnull値を表現できない。
- すると
gin
のcontext
を使ってjson化した際にも、もちろんnull値が入らない。
- jsonでnullが返せない!
package main import ( "time" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { type NotNullJSON struct { NotNullIntInit int `json:"not_null_id_init"` NotNullIntNotInit int `json:"not_null_id_not_init"` } notNullJSON := new(NotNullJSON) notNullJSON.NotNullIntInit = 1 c.JSON(200, nullableJSON) }) r.Run() } // 実行結果(何も代入していないnot_null_id_not_initにも0が入っている) {"not_null_id_init":1,"not_null_id_not_init":0}対処
- ポインタを指定すればいい
- ポインタは代入しなければnullが入っている
package main import ( "time" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { type NullableJSON struct { NullableIntInit *int `json:"nullable_id_init"` NullableIntNotInit *int `json:"nullable_id_not_init"` NotNullIntInit int `json:"not_null_id_init"` NotNullIntNotInit int `json:"not_null_id_not_init"` } nullableJSON := new(NullableJSON) nullableJSON.NotNullIntInit = 1 tmpInt := 1 // 代入方法に注意! nullableJson.NullableIDInit = &tmpInt c.JSON(200, nullableJSON) }) r.Run() } // 実行結果(何も代入していないnullable_id_not_initにnullが入っている) {"nullable_id_init":1,"nullable_id_not_init":null,"not_null_id_init":1,"not_null_id_not_init":0}おまけ・その他
- 実際は関数で計算した値などから代入することが多いと思うのでサンプルコードを置いておいた
- int以外の型でも同じ手法で対処できる。
- 使用していたのがginだったのでgin前提で書いたがechoなど多分他のフレームワークでも同じ考え方で対処できると思う。
- 投稿日:2020-02-15T01:02:40+09:00
iPhoneやiPadのアプリで無料で広告ブロックする方法
参考にした記事
iPhoneのアプリで無料で「広告」を「消す」方法 ( 広告 ブロック iPhone iPad アプリ)
広告 ブロック iPhone LINE spotify スマートニュース グノシー うざい 消し方 多い 消す ios ipad みんはや ゲーム 広告消す chrome
https://saltiga.hatenablog.com/entry/2020/02/14/221426ブロックにはDNScloakというアプリを用います
https://github.com/s-s/dnscloak
https://github.com/DNSCrypt/dnscrypt-proxy
DNS cloakはgo製の dnscrypt2のラッパー。(どちらもOSSです)
ローカルにDNSサーバーを立てて、フィルターにリスト化された広告サーバーのURLのリクエストを返さないことで広告を遮断します。
アプリで流れる動画広告などは完全に遮断することができます。
広告のあった場所は空白になります。(空白の部分がないように整形してhtmlを返すことも技術的には可能でadguardなどが行なっていますがセキリュティ的な問題があります)
実際の広告の消し方
1 app storeで「dnscloak」と「ショートカット」をインストール
[https://itunes.apple.com/jp/app/id1452162351:embed]
[https://itunes.apple.com/jp/app/id915249334:embed]
2 Safariで以下のリンクを開く(ショートカットが起動します)
[https://www.icloud.com/shortcuts/f26f84bd19e24c548079a12e0b622664:embed:cite]
3 「ショートカットを追加」をクリック
[fsaltiga:20200214215208j:plain]ショートカット 広告ブロック4 画面左下の「My workflow」を押し、「DNS cloak helper」をクリック(dns cloakが起動します)
[fsaltiga:20200214215711j:plain]dns cloak hellper 広告ブロック iPhone5 DNS cloakの画面でDNS を選び、(cloudflaireが一番速いのでおすすめ)左上の再生ボタンを押す。
[fsaltiga:20200214221056j:plain]広告ブロック dns 画面6 左上にVPNと言う表示が出たら成功です!
もしできなかった場合はコメント欄に報告してください。原因がわかれば教えます。
- 参考・引用元
- 投稿日:2020-02-15T00:47:12+09:00
GoとMongoDBとProtocol Buffersでの小数の扱い
MongoDB上で
NumberDecimal()
を使いdecimal
で小数を格納していたのだが、それを"mongo-go-driver"を介してDecode()
する時に、cannot decode 128-bit decimal into a float32 or float64 type
とエラーが出て困ったので、各々の小数周りの仕様を調べた。それぞれの小数周りの仕様へのリンク集としても役立つはず。
Go
Go上で小数に対応する型は、Numeric typesの仕様によると、
float32
とfloat64
のよう。Protocol Buffers
"proto3"のLanguage GuideにあるScalar Values Type表によると、proto上での小数表記は
float
とdoouble
で、それぞれGo上ではfloat => float32
、double => float64
となるらしい。MongoDB
MongoDB上では、schema定義のドキュメント曰く、
double
とdecimal
が存在する。これはBson Typeにあたるので、Bsonの仕様書を見てみると、どうやら64bitで表現されるのがdouble
であり、128bitで表現されるのがdecimal (decimal128)
であることがわかる。MongoDBとGoの間でやり取りするには、基本的に"mongo-go-driver"を使うことになると思うが、その中の"go.mongodb.org/mongo-driver/bson"についての箇所を見ると、BSONの
double
はfloat64
に、128-bit decimal
はprimitive.Decimal128
にエンコードされることがわかる。"mongo-go-driver"でのDecimal128の扱いを読んでみると、
BigInt()
でbig.Int
とexponential部分を返すメソッドはあるようだが、Decimal128
をGo標準のfloat64
などに変換するメソッドはないことがわかる。結論 string 最強
今回は、計算したい要件がなく、ただMongoDBから取得したデータをGoを介してProtocol Buffer経由でクライアントに送りたいだけだったので、結局
string
にすることにした。他にも、別にMongoDBに入れるのにdecimal
であることにこだわりがなければ、double
を使用することでGo上ではfloat64
として扱うことができるので、問題は発生しない。もし計算したいなら、
Decimal128
をstring
にして、それをさらにfloat64
などにするのが良いのか、BigInt()
を使って頑張れるのか("math/big"のFloat
に頑張って変換できれば良いのだが)、Decimal128
のまま計算する方法があるのかはわかっていない。何れにしても、小数周りはどの言語でも辛いんだなぁと思った。
(jsだけじゃなかった。。。