20200215のGoに関する記事は11件です。

【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プログラミング
入門Goプログラミング
posted with amazlet at 20.02.15
Nathan Youngman Roger Peppé
翔泳社
売り上げランキング: 116,400
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go + Fyne で GUI アプリケーション

Go の GUI ライブラリである Fyne について雑多にあれこれ紹介します.

Fyne とは

Fyne は Go で GUI アプリケーションを作るためのライブラリです.
シンプルでわかりやすい, 簡単に綺麗なデザインが作れる, モバイル等も含めたクロスプラットフォームといった点を売りとしています.

基本

インストール 〜 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 というものも存在します.

https://tour.fyne.io/

現状ではそれほど内容が充実しているわけではない && ブラウザ上で動作確認するような機能はないため, それほど参考にはならないかもしれません...

雑多な話題

実際に少し 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() と同じように, WindowCanvas をダミーとして生成する API も存在します.

リソースファイルの取り扱い

アイコン/画像

GUI アプリケーションでは, ボタンのアイコンとして画像を使用したい, ということがあります.
アプリケーションを配布したい場合, 当然画像ファイルなどのリソースファイルもまとめてシングルバイナリとしたいと思います.

Fyne ではこれを行うための仕組みや便利なコマンドが提供されています.

fyne コマンド

fyne に関するいくつかの操作を実行できる fyne コマンドが提供されています.

https://github.com/fyne-io/fyne/tree/master/cmd/fyne

go get でインストールします.

$ go get fyne.io/fyne/cmd/fyne

bundle

fyne bundle コマンドを利用することで, 画像等のリソースファイルを Fyne で扱えるデータ(バイト列生データを持った構造体)に変換することができます.

$ fyne bundle foo.svg > bundle.go

bundle.go の中身は以下のようなファイルになっています.

bundle.go
package 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 は現在デフォルトでは日本語での表示ができません :sob:

以下のような簡単なサンプルアプリケーションで確認してみます.
ラベル, ボタンがあり, ボタンを押すとダイアログが表示されます.

main.go
func 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.go
type 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() }
// ... 

このうち, TextFontTextBoldFont などを適切に実装することでフォントの設定が可能です.
アイコンの場合と同様に, 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


lusingander/go-gif-viewer

GIF アニメーションを再生できる単純なビューアです.
この記事で記載した, リソースファイルの組み込みやキーボードショートカットの実装などを含んでいます.

まとめ

まだまだ発展途上のライブラリであり, 不足している機能等も多いですが, 興味があれば是非触ってみてください.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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日目の乱雑でごめんな

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[geth改造]トランザクションが送られてきた時だけブロックを生成する

記事の内容

自分で勉強している時に空ブロックが大量に作られるのが煩わしかったので、トランザクションが送られてきた時だけブロックを生成するように改造しました。

環境

geth:v1.9.8-stable
golang:1.13.3

gethの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

トランザクションの送信ログとブロックの生成ログのみ出力され、このログ以降のログは出力されていません。
凄く簡単にですが、動作確認は完了です。

どうでもいいですが、コンソールだと「・・・」って出てるところをそのまま貼り付けると「?」になるんですね・・。

感想

今回はトランザクションの有無でブロックを生成するかどうか判定を加えました。
トランザクションがある場合に一度のブロック生成で処理するトランザクション数などもこの辺りの処理に追加してあげるとパフォーマンスが安定したりするのかもなんて思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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]++
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: 
        処理
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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しても壊れるのもよくわからない感じで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

package main

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write("森っている")
})

log.Faltal(http.ListenAnd(":8080", nil))

}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go(golang)/ginのAPIでnullableなjsonを返す

状況

var n int
println(n)

// 実行結果
0
  • これでどういう問題が発生するかというと構造体でも初期化した時点で勝手に0が入ってしまいnull値を表現できない。
    • するとgincontextを使って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}

おまけ・その他

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 「ショートカットを追加」をクリック

[f:id:saltiga:20200214215208j:plain]ショートカット 広告ブロック

4 画面左下の「My workflow」を押し、「DNS cloak helper」をクリック(dns cloakが起動します)

[f:id:saltiga:20200214215711j:plain]dns cloak hellper 広告ブロック iPhone

5 DNS cloakの画面でDNS を選び、(cloudflaireが一番速いのでおすすめ)左上の再生ボタンを押す。

[f:id:saltiga:20200214221056j:plain]広告ブロック dns 画面

6 左上にVPNと言う表示が出たら成功です!

もしできなかった場合はコメント欄に報告してください。原因がわかれば教えます。

  • 参考・引用元

[https://www.reddit.com/r/privacytoolsIO/:embed:cite]

[https://wikiwiki.jp/nanj-adguard/iOS%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E5%BA%83%E5%91%8A%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF:embed:cite]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の仕様によると、float32float64のよう。

Protocol Buffers

"proto3"のLanguage GuideにあるScalar Values Type表によると、proto上での小数表記はfloatdooubleで、それぞれGo上ではfloat => float32double => float64となるらしい。

MongoDB

MongoDB上では、schema定義のドキュメント曰く、doubledecimalが存在する。これはBson Typeにあたるので、Bsonの仕様書を見てみると、どうやら64bitで表現されるのがdoubleであり、128bitで表現されるのがdecimal (decimal128)であることがわかる。

MongoDBとGoの間でやり取りするには、基本的に"mongo-go-driver"を使うことになると思うが、その中の"go.mongodb.org/mongo-driver/bson"についての箇所を見ると、BSONのdoublefloat64に、128-bit decimalprimitive.Decimal128にエンコードされることがわかる。

"mongo-go-driver"でのDecimal128の扱いを読んでみると、BigInt()big.Intとexponential部分を返すメソッドはあるようだが、Decimal128をGo標準のfloat64などに変換するメソッドはないことがわかる。

結論 string 最強

今回は、計算したい要件がなく、ただMongoDBから取得したデータをGoを介してProtocol Buffer経由でクライアントに送りたいだけだったので、結局stringにすることにした。他にも、別にMongoDBに入れるのにdecimalであることにこだわりがなければ、doubleを使用することでGo上ではfloat64として扱うことができるので、問題は発生しない。

もし計算したいなら、Decimal128stringにして、それをさらにfloat64などにするのが良いのか、BigInt()を使って頑張れるのか("math/big"Floatに頑張って変換できれば良いのだが)、Decimal128のまま計算する方法があるのかはわかっていない。

何れにしても、小数周りはどの言語でも辛いんだなぁと思った。
(jsだけじゃなかった。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む