20190203のGoに関する記事は6件です。

役にたつか立たないかわからないInteliJIDEの話 【随時更新していくよ!!】

はじめに

勢いでIntelliJ All Products Pack買いました!
衝動買い楽しい。
せっかくだしちゃんと勉強しながら色々と使いこなせるようにメモします。
この機能の紹介をぜひー!とかこれ忘れてないなどあればどんどんお教えいただけますと幸いです。追記していきます!!
あと、サムライズムさんとかIntelliJとかお金もらってかいているわけではないです。

色々あるけど・・・

いろんな機能があって色々と扱えるものも違ってきます。
ちなみに最強はIntelliJ IDEA Ultimate Editionです。
plugin で追加さえすればめちゃめちゃ捗ります。

僕がweb開発とアプリする人間なので、それ以外にどうするべきなのかはわからないです。
値段は年間ですが、3年目まで毎年安くなっていきます。

JavaScripter

webStormで要件を満たせます。
年間6300円で、三年目だと3700円です。

webStormは、HTML/CSS、js, ts, coffe scriptを扱うことができます。なので、フロントエンドだろうが、バックエンド(nodeでの話)だろうがこれ一個で大丈夫です。
goに関してはプラグインでなんとかできるけど辛いみたい・・・。
そして、WebStormはかなり安いです。

ただし、こいつでDBを覗き見たりするのはpluginでできなくもないですが、Data Gripを買った方がいいかと。
二郎を食べたことない人が二郎の美味しさがわからないてきな感じでData Gripを触らない限りは他のフリーツールでも満足してやっていけます。
ただし、free trialでもData Grip触ろうものなら、もう戻ってこれなくなります。

pythoner

pyCharm professional Edition(PE)で要件を満たせます。
年間9600円で三年目だと5700円です**

pythonはCommunity Edition(CE)って無償のバージョンがありますが、これは基本的にはデータサイエンス向けでweb開発用に転用していくのは結構無理がでます。データサイエンスだけのためならCEでよいです!jupyterも動かせます!
そして、こいつはなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
なのでこれを買えば、フロンエンドだろうが、バックエンド(python or node)だろうがこれ一個で大丈夫です。
ただし、値段はちょっと高いです。

phper

phpStormです。
年間9600円で三年目だと5700円です
webStormの上位互換ににあたります。
そして、こいつもなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
なのでこれを買えば、フロンエンドだろうが、バックエンド(php or node)だろうがこれ一個で大丈夫です。
上位互換なので値段はやっぱちょっと高いです。

Rubyer

RubyMineです。
年間9600円で三年目だと5700円です**
僕はあんまりRubyに関して明るくないです・・・。
が、Rubyを扱うことができ、こいつもなんとHTML/CSS, js, ts, CoffeScriptも扱うことができます!
ということでこれもフロンエンドだろうが、バックエンド(ruby or node)だろうがこれ一個で大丈夫です。

go langer

go landです。
年間9600円で三年目だと5700円です

かっこいいですね。
これはHTML/CSS, js, tsを扱うことができます!

javer

IntelliJ IDEA ultimate Edition(UE)を使うことができます。
年間16100円で三年目だと9600円です

一応無償のCommunity Edition(UE)もありますが、で開店としてはHTML/CSS, js, markdownとかに対応していない点です。
java, kotolin, androidとかならCEで十分ですが、フロント周りをやろうとするならこれだとできないです。
pulanginをいれることで、多少python、Goなどに対して耐性をつけることもできます。
これだけ機能がたくさんあるためややお高い値段設定になっています。

swifter Objective-Cer

AppCodeがあります。
年間9600円で三年目だと5700円です

いままでXCode使ってきたので使用感はちょっと不明です・・・。というかReact Nativeエンジニアなので、そこまで必要なものでもない。
これは、swift, Objective-C/C++のほか、HTML/CSS、JavaScriptを扱えますが、ts、Coffieはできません。
なのでReact Nativeやる人からしてみてもAppCode買うかは微妙なところですね。

SQLer

Data Gripがあります。
年間9600円で三年目だと5700円です
これはめっちゃ良くてかなり重宝しています。

All Product Packは買いなの?

はっきし言ってAll Product Packは結構たかいです。
年間26900円で三年目だと16100円です

普通に開発しててもなんだかんだで2言語ぐらいできたら結構十分かと思います。
基本的に元を取ろうと思ったら3製品はちゃんと使わないともったいないです。
ですが、js系のものはだいたいどれもあつかえるので3製品使おうとするのは結構難しいです。

たとえば、僕の場合、

  • フロントエンドやる
  • アプリやるけどReact Nativeなのでswift、javaはそんなに触らない
  • バックエンド node or python
  • データサイエンスやる
  • データベースみる

とかになってくると、pyCharmとDataGripあれば十分かなと言ったところです。
それと、基本的にアップデートの速さは固有の製品の方が早いみたいなので、前線をいく人は前線をいけるような製品チョイスを心がけた方がいいかもしれません。
なので中なかなかに絶妙な値段設定をしている感じがしますね・・・。

値段のまとめ

26900円コース

All Product Pack

16100円コース

IntelliJ IDEA Ultimate Edition

9600円コース

phpStorm, RubyMine, PyCharm, DataGrip, Goland, AppCode

6300円コース

webStorm

と言った感じです。なので、ここから自分に何が必要なのかを吟味して選んでいくといいかと思います!
あと、僕みたいに心配性だったり考えるのめんどくさ買ったりする人はAll Product Packを買うといいと思います。

intention Actions

option + enter
いろんなことができます。
例えば、勢いでコード書いたけどファイルつくってなかった時とか、ここからさささっと作ることができます。

nazo2.gif

post completion

あと出しでコードを書く機能。
みてもらったほうが早い機能。そんなにいらないんじゃないって思うかもですが、なんだかんだ言って覚えれば便利な機能です。

nazo2.gif

一覧はpreference > editor > general > Postfix Completionからみれます。

スクリーンショット 2019-02-05 14.41.30.png

独自定義もできます。

スクリーンショット 2019-02-05 14.48.19.png

割と便利な

console.log({
  a,
  b,
});

を作りました。
nazo2.gif

live template

色々な機能がある。アロー関数をfarrowとして追加してみた。
こちらを参考にした。

タブを押すたびに引数、処理と言ったことができる。

nazo2.gif

スクリーンショット 2019-02-03 13.56.22.png

こんな感じで追加する。ワーニングっぽいのでてたらどの言語でできているかわからないっていっているので、どの言語で使えるかを設定してあげましょう。

($PARAMS$) => ({
    $BODY$
})$END$;

こんな感じで定義すると、param -> body -> endにtabを押すたびに遷移する。
一応、テンプレート的なのは、範囲を選択 -> tools -> save as livetemplateっていうのを押すと、選択範囲のコードが入った状態で始められる親切設計。

expand selection

いい感じに選択範囲をする機能です。
option + ↑で回数に応じて範囲が広がります。逆に戻したい場合はoption + ↓です。

nazo2.gif

変数化とインライン化

名前的に変数化したかったり、戻したりを一瞬でできます。

cmd + option + v 変数化します。
cmd + option + n で戻します。

nazo2.gif

parameter hint name

若干物議をかもすこの機能。
スクリーンショット 2019-02-05 15.14.10.png

こんな形でパラーメーター名を表示できます。

設定はここで切り替えられます。

スクリーンショット 2019-02-05 15.13.44.png

ショートカット

効果 コマンド
Undo cmd + z
Redo cmd + shift + z
検索 cmd + f or cmd + shift + f (プロジェクト全体)
置換 cmd + r or cmd + shift + r (プロジェクト全体)
複製 cmd + d
プロジェクトにwindowを開く閉じる cmd + 1
ファイル作成 cmd + n
コードの折りみと展開 cmd + .
preferenceを開く cmd + ,
help or Action cmd + shift + a
expand selection option + ↑ (回数で変わる)
予測補完を出す(macだとデフォで行けないので後述。ここに書いたのは自分用メモ) cmd + 4
smart補完を出す(macだとデフォで行けないので後述。ここに書いたのは自分用メモ) cmd + 5
intention Actions option + enter
変数化 cmd + option + v
インライン化 cmd + option + n
パラメーターヒント cmd + p
定義元ジャンプ(とにかく素晴らしい) cmd + b
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goルーチンに入門してみた

酒井潤さんがUdemyで講師をしている以下の講座のGoルーチンに関するレクチャがとてもわかりやすかったので、学習したことをまとめています。

[講座名]
現役シリコンバレーエンジニアが教えるGo入門 + 応用でビットコインのシストレFintechアプリの開発
https://www.udemy.com/go-fintech/

私もまだ全部を勉強したわけではなかったですが、文法から実際のアプリ開発まで非常によくまとまっていてわかりやすいです。

Goに関する書籍やスクールが日本で広く普及されていない中でのコスパは最高だと思ってます。

Goルーチン(というか非同期処理全般)は奥が深いので、必要に応じて使っていこうかと思う次第です。
もともとGoを学ぼうと思った動機が、簡単なコマンドラインツール(シェル)的にRubyでできるような少し凝ったことを型を使って出来たらいいぐらいの感じで使いたかったので、私はそんなに使わないかもしれません。

Goルーチンで実行する関数に戻り値がない場合

基本的な実行

ダサいですが、time.sleep関数を使っています。

package main

import (
    "fmt"
    "time"
)

// goルーチンで実行する関数
func asyncFunc(s string) {
    for i := 0; i < 5; i ++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

// メイン関数
func main() {
    // goルーチンの関数の実行
    for i := 0 ; i < 3; i ++ {
        str := fmt.Sprintf("Go routine (no: %v)", i)
        go asyncFunc(str)
    }

    // time.sleepしないとgoルーチンの実行完了よりも先にメイン関数の処理が完了してしまう。
    time.Sleep(1 * time.Second)
}

実行結果は以下のとおりです。

Go routine (no: 2)
Go routine (no: 1)
Go routine (no: 0)
Go routine (no: 1)
Go routine (no: 0)
Go routine (no: 2)
Go routine (no: 0)
Go routine (no: 1)
Go routine (no: 2)
Go routine (no: 1)
Go routine (no: 2)
Go routine (no: 0)
Go routine (no: 1)
Go routine (no: 2)
Go routine (no: 0)

sync.WaitGroupを使って実行完了を待つ

time.sleep関数で待つやり方はどのくらいsleepすればいいかわからないので、sync.WaitGroupでGoルーチンで実行する関数の完了を待つ方法。

package main

import (
    "fmt"
    "sync"
    "time"
)

// goルーチンで実行する関数
func asyncFunc(s string, wg*sync.WaitGroup) {
    defer wg.Done() // WaitGroupを最後に完了しないといけない。
    for i := 0; i < 5; i ++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

// メイン関数
func main() {
    // goルーチンで非同期に実行される処理を待つために
    // WaitGroupを使う。
    var wg sync.WaitGroup

    // goルーチンの関数の実行
    for i := 0 ; i < 3; i ++ {
        wg.Add(1) // goルーチンを実行する関数分だけAddする。
        str := fmt.Sprintf("Go routine (no: %v)", i)
        go asyncFunc(str, &wg)
    }

    // goルーチンで実行される関数が終了するまで待つ。
    wg.Wait()
}

実行結果は以下のとおりです。

Go routine (no: 2)
Go routine (no: 0)
Go routine (no: 1)
Go routine (no: 1)
Go routine (no: 2)
Go routine (no: 0)
Go routine (no: 0)
Go routine (no: 2)
Go routine (no: 1)
Go routine (no: 0)
Go routine (no: 1)
Go routine (no: 2)
Go routine (no: 2)
Go routine (no: 1)
Go routine (no: 0)

Goルーチンで実行する関数に戻り値がある場合

チャネル

チャネルを介して結果を受け取るためにチャネルを使います。

goルーチンの関数にチャネルを渡し、処理を非同期で実行し、結果を受け取る際には、ブロッキングして結果を受け取っています。

package main

import "fmt"

// goルーチンの関数
func asyncFunc(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum
}

func main() {
    // スライスの生成
    s := []int{1, 2, 3, 4, 5}

    // チャネルの生成
    // チャネルを介してgoルーチンの関数とやりとりします。
    c := make(chan int)

    // goルーチンの関数実行。
    // 引数にチャネルを渡している。
    go asyncFunc(s, c)
    go asyncFunc(s, c)

    // 結果を受け取る
    x := <-c // ここで処理がブロッキングされる
    fmt.Println(x)

    y := <-c // ここで処理がブロッキングされる
    fmt.Println(y)
}

バッファーチャネル

チャネルにバッファサイズを定義することもできる。
goルーチンの関数側でチャネルをクローズする必要があります。

package main

import "fmt"

func asyncFunc(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
        c <- sum // チャネルに演算結果を渡している
    }
    close(c) // closeで閉じること!
}

func main() {
    s := []int{1, 2, 3, 4, 5}

    c := make(chan int, len(s)) // チャネルのバッファサイズ: 5
    go asyncFunc(s, c)

    // チャネルのバッファサイズ分だけループ処理
    for i := range c {
        fmt.Println(i)
    }
}

結果は以下のとおり。

1
3
6
10
15

上記のコードでチャネルのクローズ処理close(c)を忘れると、以下のようなデッドロックエラーが発生する。

1
3
6
10
15
fatal error: all goroutines are asleep - deadlock!

Goルーチンを使った代表的なメッセージングパターン

Producer/Consumerパターン

前述の知識を流用した応用的なパターンです。

sync.WaitGroupとチャネルを使ってProducer/Consumerパターンを実装。チャネルがメッセージキューのような役割を果たしています。
ひとつのチャネル(キュー相当)をProducerとConsumerで共有していることがポイントです。

package main

import (
    "fmt"
    "sync"
    "time"
)

// Producer
func producer(ch chan int, i int) {
    ch <- i * 2
}

// Consumer
func consumer(no int, ch chan int, wg *sync.WaitGroup) {
    for i := range ch {
        func() {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("process no: %v result: %v\n", no, i*1000)
        }()
    }
}

func main() {
    var wg sync.WaitGroup

    ch := make(chan int)

    // Producerでメッセージを生成する
    for i := 0; i < 100; i ++ {
        wg.Add(1)
        go producer(ch, i)
    }

    // Consumerがメッセージをポーリング
    // consumerの数を3つに設定
    for i := 0; i < 3; i ++ {
        go consumer(i, ch, &wg)
    }

    wg.Wait() // 10回Addされた分だけ待つ。
    close(ch) // 閉じないとConsumerが待ち続ける

    fmt.Println("Done")
}

process 2000
process 0
process 8000
process 4000
process 6000
process 10000
process 12000
process 14000
process 16000
process 18000
Done

fan-out/fan-inパターン

パイプライン(fan-out/fan-in)を使った並列処理も以下のように実装できます。

package main

import "fmt"

// stage1 fan-in
// firstというチャネルにメッセージを詰める
func producer(first chan int) {
    defer close(first) // firstチャネルにメッセージを詰め終わったらチャネルをクローズ

    for i := 0; i < 10; i ++ {
        first <- i
    }
}

// stage2 fan-out, fan-in
// firstチャネルからメッセージを取り出し、
// 演算後にsecondチャネルにメッセージを詰める。
// <-はなくてもいい。
func multi2(first <-chan int, second chan<- int) {
    defer close(second) // secondチャネルにメッセージを詰め終わったらチャネルをクローズ
    for i := range first {
        second <- i * 2
    }
}

// stage3 fan-out, fan-in
// secondチャネルからメッセージを取り出し、
// 演算後にthirdチャネルにメッセージを詰める。
func multi4(second <-chan int, third chan<- int) {
    defer close(third)
    for i := range second {
        third <- i * 2
    }
}

func main() {
    // チャネルの生成
    first := make(chan int)
    second := make(chan int)
    third := make(chan int)

    go producer(first)
    go multi2(first, second)
    go multi4(second, third)

    // パイプラインの結果をthirdチャネルから取り出して表示する
    for result := range third {
        fmt.Println(result)
    }
}

結果は以下のとおりです。

0
4
8
12
16
20
24
28
32
36

for-selectを使ったチャネルからのメッセージ受信

for-selectを使って同時にブロッキングせずにチャネルからメッセージを受信することができます。
処理の内容・結果の異なるgoルーチンの関数の結果を同時に処理するためのパターンかと思います。

package main

import (
    "fmt"
    "time"
)

func goroutine1(ch chan string)  {
    for {
        ch <- "packet from 1"
        time.Sleep(2 * time.Second)
    }
}

func goroutine2(ch chan int)  {
    for {
        ch <- 100
        time.Sleep(1 * time.Second)
    }
}

func main() {
    c1 := make(chan string)
    c2 := make(chan int)
    go goroutine1(c1)
    go goroutine2(c2)

    // selectを使って同時にブロッキングせずに受信する。
    for {
        select {
        case msg1 := <- c1:
            fmt.Println(msg1)
        case msg2 := <- c2:
            fmt.Println(msg2)
        }
    }
}
100
packet from 1
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
100
packet from 1
100
...

SyncのMutex

複数スレッドからの同時更新、同時参照時の不整合、デッドロックを回避するために、SyncのMutexを利用できます。

package main

import (
    "fmt"
    "sync"
    "time"
)

// mutexを持つ構造体を定義する。
type Counter struct {
    v   map[string]int
    mux sync.Mutex // mutex
}

// インクリメント
func (c *Counter) Inc(key string) {
    c.mux.Lock()         // ロックする。
    defer c.mux.Unlock() // ロックを解除する。
    c.v[key]++
}

// 値をゲットする。
func (c *Counter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}

func main() {
    /* 以下のgoルーチンは同時に書き込むとエラーが発生します。
    c := make(map[string]int)

    go func() {
        for i := 0; i< 10; i++ {}
        c["key"] += 1
    }()

    go func() {
        for i := 0; i< 10; i++ {}
        c["key"] += 1
    }()

    time.Sleep(1 * time.Second)
    fmt.Println(c, c["key"])
    */


    // 以下はMutexを使って不整合が発生しないようにした書き方。
    c := Counter{v: make(map[string]int)}
    go func() {
        for i := 0; i< 10; i++ {
            c.Inc("key")
        }
    }()

    go func() {
        for i := 0; i< 10; i++ {
            c.Inc("key")
        }
    }()

    time.Sleep(1 * time.Second)
    fmt.Println(c, c.Value("key"))
}
{map[key:20] {0 0}} 20
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめてのgoa�

goaについて

Goを使ってREST APIを用意したいときに利用できるマイクロサービス用のフレームワークの一つです。
ビジネスロジック中心で開発ができる、swaggerのドキュメントが自動生成されるなどの利点があります。

goaのgithubはこちらにあります。

サンプルについて

今回用意したサンプルはこちらです。

環境設定等

mercurialのインストール

依存関係の解決にあたり、depを使ったが、Macdep ensureをすると、なぜかフリーズしたので、pstreeで確認すると下記の箇所で固まっていました。

$ pstree 69752
-+= 69752 xxx dep ensure -v
 \-+= 71086 xxx /Applications/Xcode.app/Contents/Developer/usr/bin/git ls-remote ssh://git@bitbucket.org/pkg/inflect
   \--- 71087 xxx /usr/bin/ssh git@bitbucket.org git-upload-pack '/pkg/inflect'

調べたところ、mercurialが必要とのことだったので、brewを実行してインストールしました。

brew install mercurial

goadepのインストール

goadepgo getする。

$ go get -u github.com/goadesign/goa/...
$ go get -u github.com/golang/dep/cmd/dep

goaを使ったREST API設計

API、Resource、MediaTypeの定義

designの定義

1つのgoファイルで集約しても問題ないのですが、便宜上複数のファイルの分割しました。

APIのベースとなる基本的な定義です。

./design/api_base.go
package design

import (
    . "github.com/goadesign/goa/design/apidsl"
)

var _ = API("goa-sample", func() {
    Title("The Sample API")
    Description("A simple goa service")
    Version("v1")
    Scheme("http", "https")
    BasePath("/api/v1")
    Consumes("application/json")
    Produces("application/json")
    Host("localhost:8080")

    Origin("http://localhost:8080/swagger", func() {
        Expose("X-Time")
        Methods("GET", "POST", "PUT", "PATCH", "DELETE")
        MaxAge(600)
        Credentials()
    })
})

swaggerの定義です。
この例では、http://localhost:8080/swagger-uiにアクセスすると、swaggerのドキュメントを閲覧できます。
なお、swagger-uiをダウンロードし、public/swagger-ui/distに配置する必要があります。

./design/swagger.go
package design

import (
    . "github.com/goadesign/goa/design/apidsl"
)

// Swagger routing
var _ = Resource("swagger", func() {
    Origin("*", func() {
        Methods("GET")
    })
    Files("/swagger.json", "swagger/swagger.json")
    Files("/swagger.yaml", "swagger/swagger.yaml")
    Files("/swagger-ui/*filepath", "public/swagger-ui/dist")
})

続いて、レスポンスデータの型を定義します。

design/media_types.go
package design

import (
    "time"

    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"

)

var MediaSamples = MediaType("application/vnd.samples+json", func() {
    Description("sample list")
    Attribute("id", Integer, "id", func() {
        Example(1)
    })
    Attribute("name", String, "名前", func() {
        Example("サンプル1")
    })
    Attribute("created_at", DateTime, "作成日", func() {
        loc, _ := time.LoadLocation("Asia/Tokyo")
        Example(time.Date(2019, 01, 31, 0, 0, 0, 0, loc).Format(time.RFC3339))
    })
    Attribute("updated_at", DateTime, "更新日", func() {
        loc, _ := time.LoadLocation("Asia/Tokyo")
        Example(time.Date(2019, 01, 31, 12, 30, 50, 0, loc).Format(time.RFC3339))
    })
    Required("id", "name", "created_at", "updated_at")
    View("default", func() {
        Attribute("id")
        Attribute("name")
        Attribute("created_at")
        Attribute("updated_at")
    })
})

var MediaSample = MediaType("application/vnd.sample+json", func() {
    Description("sample detail")
    Attribute("id", Integer, "sample id", func() {
        Example(1)
    })
    Attribute("user_id", Integer, "user id", func() {
        Example(1)
    })
    Attribute("name", String, "名前", func() {
        Example("サンプル1")
    })
    Attribute("detail", String, "詳細", func() {
        Example("サンプル1の詳細")
    })
    Attribute("created_at", DateTime, "作成日", func() {
        loc, _ := time.LoadLocation("Asia/Tokyo")
        Example(time.Date(2019, 01, 31, 0, 0, 0, 0, loc).Format(time.RFC3339))
    })
    Attribute("updated_at", DateTime, "更新日", func() {
        loc, _ := time.LoadLocation("Asia/Tokyo")
        Example(time.Date(2019, 01, 31, 12, 30, 50, 0, loc).Format(time.RFC3339))
    })
    Required("id", "user_id", "name", "detail", "created_at", "updated_at")
    View("default", func() {
        Attribute("id")
        Attribute("user_id")
        Attribute("name")
        Attribute("detail")
        Attribute("created_at")
        Attribute("updated_at")
    })
})

最後にAPIの引数とレスポンスの内容を記載します。

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

var _ = Resource("samples", func() {
    BasePath("/samples")
    Action("list", func() {
        Description("複数")
        Routing(
            GET("/"),
        )
        Params(func() {
            Param("user_id", Integer, "user id", func() {
                Example(1)
            })
        })
        Response(OK, CollectionOf(MediaSamples))
        Response(NotFound)
        Response(BadRequest, ErrorMedia)
    })
    Action("show", func() {
        Description("単数")
        Routing(
            GET("/:id"),
        )
        Params(func() {
            Param("user_id", Integer, "user id", func() {
                Example(1)
            })
            Param("id", Integer, "sample data id", func() {
                Example(123)
            })
        })
        Response(OK, CollectionOf(MediaSample))
        Response(NotFound)
        Response(Unauthorized)
        Response(BadRequest, ErrorMedia)
    })
    Action("add", func() {
        Description("追加")
        Routing(
            POST("/"),
        )
        Payload(func() {
            Attribute("user_id", String, "user id", func() {
                Example("12345")
            })
            Attribute("name", String, "name of sample", func() {
                Example("sample1")
            })
            Attribute("detail", String, "detail of sample", func() {
                Example("sample1の詳細")
            })
            Required("user_id", "name", "detail")
        })
        Response(OK, CollectionOf(MediaSample))
        Response(NotFound)
        Response(Unauthorized)
        Response(BadRequest, ErrorMedia)
    })
    Action("delete", func() {
        Description("削除")
        Routing(
            DELETE("/:id"),
        )
        Params(func() {
            Param("id", Integer, "sample id", func() {
                Example(1)
            })
        })
        Response(NoContent)
        Response(NotFound)
        Response(Unauthorized)
        Response(BadRequest, ErrorMedia)
    })
    Action("update", func() {
        Description("更新")
        Routing(
            PUT("/:id"),
        )
        Params(func() {
            Param("id", Integer, "sample id")
        })
        Payload(func() {
            Param("name", String, "name of sample", func() {
                Example("sample1")
            })
            Param("detail", String, "detail of sample", func() {
                Example("sample1")
            })
            Required("name", "detail")
        })
        Response(NoContent)
        Response(NotFound)
        Response(BadRequest, ErrorMedia)
    })
})

goagenの実行

下記コマンドを実行すると諸々ファイルが自動生成されます。

$ goagen bootstrap -d github.com/hiroykam/goa-sample/design

sample.goswagger.go./controllerに移動させます。

Modelの定義

自動生成する場合

まずは、./design/models.goを用意します。
idstringにしたい場合、gorma.UUIDとします。
deleted_atを指定すると、gormdeleteを実行した際、論理削除になります。

./design/models.go
package design

import (
    "github.com/goadesign/gorma"
    . "github.com/goadesign/gorma/dsl"
)

var _ = StorageGroup("goa-sample", func() {
    Description("Sample Model")
    Store("MySQL", gorma.MySQL, func() {
        Description("MySQL models")
        Model("Sample", func() {
            RendersTo(MediaSample)
            Description("sample table")
            Field("id", gorma.Integer)
            Field("user_id", gorma.Integer)
            Field("name", gorma.String)
            Field("detail", gorma.String)
            Field("created_at", gorma.Timestamp)
            Field("updated_at", gorma.Timestamp)
            Field("deleted_at", gorma.NullableTimestamp)
        })
    })
})
$ goagen --design=github.com/hiroykam/goa-sample/design gen --pkg-path=github.com/goadesign/gorma

models/sample.go
models/sample_helper.go

実行すると下記ファイルが生成されますが、上位層にエラーをそのまま伝搬している、このサンプルの例ですとuser_idで絞り込むGetListを別に用意する必要があるなどの手間は発生します。
またmodelを追加し、上記コマンドを実行しますとsample.goが新規に作成されてしまいますので、./model/sample_extention.goといったように別ファイルで定義する必要があります。

models/sample.go
// Code generated by goagen v1.3.1, DO NOT EDIT.
//
// API "goa-sample": Models
//
// Command:
// $ goagen
// --design=github.com/hiroykam/goa-sample/design
// --out=$(GOPATH)/src/github.com/hiroykam/goa-sample
// --version=v1.3.1

package models

import (
    "context"
    "github.com/goadesign/goa"
    "github.com/hiroykam/goa-sample/app"
    "github.com/jinzhu/gorm"
    "time"
)

// sample table
type Sample struct {
    ID        int `gorm:"primary_key"`
    Detail    string
    Name      string
    UserID    int
    CreatedAt time.Time  // timestamp
    DeletedAt *time.Time // nullable timestamp (soft delete)
    UpdatedAt time.Time  // timestamp
}

// TableName overrides the table name settings in Gorm to force a specific table name
// in the database.
func (m Sample) TableName() string {
    return "samples"

}

// SampleDB is the implementation of the storage interface for
// Sample.
type SampleDB struct {
    Db *gorm.DB
}

// NewSampleDB creates a new storage type.
func NewSampleDB(db *gorm.DB) *SampleDB {
    return &SampleDB{Db: db}
}

// DB returns the underlying database.
func (m *SampleDB) DB() interface{} {
    return m.Db
}

// SampleStorage represents the storage interface.
type SampleStorage interface {
    DB() interface{}
    List(ctx context.Context) ([]*Sample, error)
    Get(ctx context.Context) (*Sample, error)
    Add(ctx context.Context, sample *Sample) error
    Update(ctx context.Context, sample *Sample) error
    Delete(ctx context.Context) error

    ListSample(ctx context.Context) []*app.Sample
    OneSample(ctx context.Context) (*app.Sample, error)
}

// TableName overrides the table name settings in Gorm to force a specific table name
// in the database.
func (m *SampleDB) TableName() string {
    return "samples"

}

// CRUD Functions

// Get returns a single Sample as a Database Model
// This is more for use internally, and probably not what you want in  your controllers
func (m *SampleDB) Get(ctx context.Context) (*Sample, error) {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "get"}, time.Now())

    var native Sample
    err := m.Db.Table(m.TableName()).Where("").Find(&native).Error
    if err == gorm.ErrRecordNotFound {
        return nil, err
    }

    return &native, err
}

// List returns an array of Sample
func (m *SampleDB) List(ctx context.Context) ([]*Sample, error) {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "list"}, time.Now())

    var objs []*Sample
    err := m.Db.Table(m.TableName()).Find(&objs).Error
    if err != nil && err != gorm.ErrRecordNotFound {
        return nil, err
    }

    return objs, nil
}

// Add creates a new record.
func (m *SampleDB) Add(ctx context.Context, model *Sample) error {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "add"}, time.Now())

    err := m.Db.Create(model).Error
    if err != nil {
        goa.LogError(ctx, "error adding Sample", "error", err.Error())
        return err
    }

    return nil
}

// Update modifies a single record.
func (m *SampleDB) Update(ctx context.Context, model *Sample) error {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "update"}, time.Now())

    obj, err := m.Get(ctx)
    if err != nil {
        goa.LogError(ctx, "error updating Sample", "error", err.Error())
        return err
    }
    err = m.Db.Model(obj).Updates(model).Error

    return err
}

// Delete removes a single record.
func (m *SampleDB) Delete(ctx context.Context) error {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "delete"}, time.Now())

    var obj Sample
    err := m.Db.Delete(&obj).Where("").Error

    if err != nil {
        goa.LogError(ctx, "error deleting Sample", "error", err.Error())
        return err
    }

    return nil
}
models/sample_helper.go
// Code generated by goagen v1.3.1, DO NOT EDIT.
//
// API "goa-sample": Model Helpers
//
// Command:
// $ goagen
// --design=github.com/hiroykam/goa-sample/design
// --out=$(GOPATH)/src/github.com/hiroykam/goa-sample
// --version=v1.3.1

package models

import (
    "context"
    "github.com/goadesign/goa"
    "github.com/hiroykam/goa-sample/app"
    "github.com/jinzhu/gorm"
    "time"
)

// MediaType Retrieval Functions

// ListSample returns an array of view: default.
func (m *SampleDB) ListSample(ctx context.Context) []*app.Sample {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "listsample"}, time.Now())

    var native []*Sample
    var objs []*app.Sample
    err := m.Db.Scopes().Table(m.TableName()).Find(&native).Error

    if err != nil {
        goa.LogError(ctx, "error listing Sample", "error", err.Error())
        return objs
    }

    for _, t := range native {
        objs = append(objs, t.SampleToSample())
    }

    return objs
}

// SampleToSample loads a Sample and builds the default view of media type Sample.
func (m *Sample) SampleToSample() *app.Sample {
    sample := &app.Sample{}
    sample.CreatedAt = m.CreatedAt
    sample.Detail = m.Detail
    sample.ID = m.ID
    sample.Name = m.Name
    sample.UpdatedAt = m.UpdatedAt
    sample.UserID = m.UserID

    return sample
}

// OneSample loads a Sample and builds the default view of media type Sample.
func (m *SampleDB) OneSample(ctx context.Context) (*app.Sample, error) {
    defer goa.MeasureSince([]string{"goa", "db", "sample", "onesample"}, time.Now())

    var native Sample
    err := m.Db.Scopes().Table(m.TableName()).Where("").Find(&native).Error

    if err != nil && err != gorm.ErrRecordNotFound {
        goa.LogError(ctx, "error getting Sample", "error", err.Error())
        return nil, err
    }

    view := *native.SampleToSample()
    return &view, err
}

手動で頑張る場合

サンプルではmodelsserviceを用意しました。
diも用意すべきかもしれません。

Controllerの実装

下記コマンドで雛形が作成されます。
コマンド再実行により、ここに追記したコードは削除されません。

$ goagen controller -d github.com/hiroykam/goa-sample/design --out=./controller

Resource、MediaTypeの変更

Resource、MediaTypeを変更した際、下記を実行します。

$ goagen app -d github.com/hiroykam/goa-sample/design
$ goagen swagger -d github.com/hiroykam/goa-sample/design
$ goagen controller -d github.com/hiroykam/goa-sample/design --out=./controller

goaの起動

make

サンプルで下記のコマンドで、docker-compose buildします。

$ make docker-build

ビルド完了後、下記のコマンドで起動できます。

$ make docker-up

swagger-ui

起動後、http://localhost:8080/swagger-ui/にアクセスすると下記が表示されます。

スクリーンショット 2019-02-03 15.06.17.png

REST API

Postmanを使いました。

スクリーンショット 2019-02-03 18.04.15.png

その他

goagenに失敗する

goagenを実行すると、下記のようなエラーが発生する時があります。

exit status 1
missing API definition, make sure design is properly initialized
make: *** [_controller] Error 1

githubissueで散見されていましたが、簡単な解決方法はvendorディレクトリを一旦削除すると解決しました。

depを使っている場合、goagenに失敗する

issueを拝見する限りですと、depの問題といった記載があり、Gopkg.tomlに下記を追記すると解決できる。
depを使わないほうが良いのでしょうか・・・

[[override]]
  name = "github.com/satori/go.uuid"
  revision = "master"

エラーの内容が分かりづらい

例えば、./design/media_types.go で下記のような属性を定義します。

    Attribute("created_at", DateTime, "作成日", func() {
        Example("2019-01-01")
    })

そこで、goagenしても、下記のようなエラーが表示されますが、内容からRFC3339フォーマットに準拠していないのが原因であることが分かりませんでした。

$ goagen app -d github.com/hiroykam/goa-sample/design
exit status 1
[design/media_types.go:24] example value "2019-01-01" is incompatible with attribute of type string

log出力が扱いづらい

main.go でこのようにstodoutJSON形式のログ出力をしたかったが、reqest IDなどの情報まで出力することができませんでした。
また、goa内部で出力するログは別のloggerが使われているため、思うようなログ出力が期待できませんでした。

    logger := log15.New(log15.Ctx{"module": "goa-sample"})
    logger.SetHandler(log15.StreamHandler(os.Stdout, log15.JsonFormat()))
    service.WithLogger(goalog15.New(logger))

そのため、サンプルではjson出力するloggerを別途用意しました。

trailing slashの扱いについて

URLの末尾に/ありとなしでAPIを実行すると、意図していないAPIが呼び出されるケースがありました(調査中)。

参考

こちらの記事をベースに勉強をさせていただきました。

今後

  • テストコードを書く
  • cicrleciなどの環境を整える
  • jwtで認証
  • Makefile、READMEの修正・更新
  • React/ReduxからAPIを呼び出す
  • gorm.ErrRecordNotFoundが検出できない原因調査
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

何故Go言語を私は使いたいのか? 良い点と悪い点を雑にまとめる

前書き

選択肢は多いし、自分がやりたいことを実現するには"どちらが適切か"を踏まえると、
言語を選ぶことはなかなか難しいものです。

Go言語で開発をやろうと思った時に、何故Go言語でやりたいのか、
また、どういう点でイケてないと思っているのかを
思考の整理のためにまとめた記事になります。

この記事は、ベストなプログラミング言語だと主張する記事ではありません。
ご自身でで確かな情報に基づいて言語の調査をする際の助けになれば幸いです。

良い点

1. パフォーマンス

  • Ruby, pythonなどと比較すると良い

2. デプロイの容易さ

  • 多くの場合、単一ファイルで良い。
    ただしコンテナをデプロイできる時代なので優位性は(ry

3. 多くの標準ライブラリ

  • net, encoding, html, os, etc...

4. 周辺ツールの充実

  • Gofmtによるコードフォーマットの強制

5. シンプルな言語仕様

  • 学習コストが比較的少ない

6. スレッドモデルによる並列処理

  • イベントモデルは辛い (主観)

7. コンパイルによるバグの見つけやすさ

  • go vetでコードの静的解析を行うことができる(標準ツール)

8. コンパイルの速さ

  • C++, javaと比較 するとコンパイルが早い

9. 移植性が良い

  • 内部エンコーディングはutf-8

10. Googleが作ってる安心感

悪い点

1. シンプルな言語実装だが、ハマりどころが少ないわけではない 1  2

  • nilインターフェースがnilじゃない
  • 変数のシャドーイングの挙動 etc...

2. シンプルすぎてプログラミング言語として色々不足を感じる(主観)

  • 例外がない
  • 継承がない
  • Genericsがない(次のverで追加?) etc...

3. 学んだ所で言語として特に新しい発見があまりない 3

  • 1970年代で止まった言語に見える
  • 40年間のプログラミング言語の研究を排除した最新の言語
  • Goはブルーカラー(普通)の開発者のための言語
    • ただし開発者には2パターンいる
    • できるだけ簡単かつ迅速に作業を進めたいという開発者
    • 最新の「革新的」言語を追いかけている開発者

  1. なぜGo言語は設計が悪いのか – Go愛好者の見地から https://postd.cc/why-go-is-a-poorly-designed-language/ 

  2. 7 common mistakes in Go and when to avoid them by Steve Francia (Docker) https://www.youtube.com/watch?v=29LLRKIL_TI 

  3. Why Everyone Hates Go (https://npf.io/2014/10/why-everyone-hates-go/) (かなり古い) 

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

スターティングGo言語�7章のメモ - os/time

Goの標準パッケージを入門レベルで学ぼうと思って書き残したメモです。
スターティングGo言語の7章の以下のパッケージについて記載しています。
単なる写経に近いクソ記事ですw。

  • osパッケージ
  • timeパッケージ

osパッケージ

import "os"

ホスト名の取得

func main() {
    host, _ := os.Hostname()
    fmt.Println(host)
}

環境変数

func main() {
    // 環境変数一覧を表示
    for _, env := range os.Environ(){
        fmt.Println(env)
    }

    // 環境変数の名前を指定して取得
    fmt.Println(os.Getenv("HOME"))
}

プログラムの終了

func main() {
    // 終了コード1で終了する
    os.Exit(1)
}

プロセス情報

func main() {
    fmt.Printf("プロセスID: %v\n", os.Getpid())
    fmt.Printf("親プロセスID: %v\n",os.Getppid())
    fmt.Printf("ユーザID: %v\n", os.Getuid())
    fmt.Printf("グループID: %v\n", os.Getgid())
}

log.Fatal

func main()  {
    _, err := os.Open("README.md")
    if err != nil {
        log.Fatal(err)
    }
}

コマンドライン引数

func main()  {
    for _, v := range os.Args {
        fmt.Println(v)
    }
}

ファイル操作

読み込み専用ファイルのオープン

func main()  {
    f, err := os.Open("README.md")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
}

os.File

func main()  {
    f, err := os.Open("README.md")
    if err != nil {
        log.Fatal(err)
    }

    /* []byte型のスライスにファイルの内容を読み込む */
    bs1 := make([]byte,128)
    n ,err := f.Read(bs1) // nは実際に読み込んだバイト数
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(n)

    /* ファイルのオフセットを指定して読み込む */
    bs2 := make([]byte, 128)
    n2, err := f.ReadAt(bs2, 10) // 10バイト目から読み込む
    if err != nil {
        log.Println(err)
    }
    fmt.Println(n2)

    /* ファイルのステータス取得 */
    fi, err := f.Stat()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(fi.Name()) // ファイル名
    fmt.Println(fi.Size()) // ファイルサイズ
    fmt.Println(fi.Mode()) // ファイルのモード
    fmt.Println(fi.ModTime()) // ファイルの最終更新時間
    fmt.Println(fi.IsDir()) // ディレクトリかどうか

}

実際にファイルを読み込む処理を書くと、こんなかんじでしょうか。

func readFile(filepath string) (lines []string) {
    f, err := os.Open(filepath)

    if err != nil {
        log.Fatal(err)
    }

    buf := make([]byte, 1024)

    for {
        n, err := f.Read(buf)
        if err != nil {
            log.Fatal(err)
        }
        if err == io.EOF { // EOFの場合抜ける
            break
        }
        str := string(buf[:n])
        fmt.Println(str)
        lines = append(lines, str)
    }
    return
}

func main() {
    filepath := "README.md"

    for _, line := range readFile(filepath) {
        fmt.Println(line)
    }
}

新規ファイルの作成

func main() {
    f, err := os.Create("NewFile.txt") // ファイル名を指定して新規作成
    if err != nil {
        log.Fatal(err)
    }

    f.Write([]byte("Hello, World\n")) // ファイルに[]byte型の内容を書き込み
    f.WriteAt([]byte("Golang"), 7)    // オフセットを指定して書き込み
    f.Seek(0, io.SeekEnd)             // ファイルの末尾にオフセットを移動
    f.WriteString("Year")             // 文字列をファイルに書き込み
}

ファイルオープン詳細

os.OpenFileを使う。

func main() {
    f, err := os.OpenFile("README.md", os.O_RDONLY, 0666)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(f.Name())
    defer f.Close()
}
フラグ 意味
O_RDONLY 読み込み専用
O_WRONLY 書き込み専用
O_RDWR 読み書き可能
O_APPEND ファイルの末尾に追記
O_CREATE ファイルが存在しなければ新規作成
O_TRUNC 可能であればファイルの内容をオープン時に空にする

ファイルの削除

func main() {
    if err := os.Remove("NewFile.txt"); err != nil {
        log.Fatal(err)
    }
}

ファイル名の変更と移動

func main() {
    if err := os.Rename("NewFile.txt", "BackupFile.txt"); err != nil {
        log.Fatal(err)
    }
}

ディレクトリ操作

カレントディレクトリ

func main() {
    dir, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(dir)
}

ディレクトリの読み込み

func main() {
    f, err := os.Open(".")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // カレントディレクトリ下のディレクトリ名を列挙
    fis, err := f.Readdir(0)
    for _, fi := range fis {
        if fi.IsDir() {
            fmt.Println(fi.Name())
        }
    }
}

ディレクトリの作成

func main() {
    // カレントディレクトリ配下にディレクトリを作成
    if err := os.Mkdir("foo", 0775); err != nil {
        log.Fatal(err)
    }

    // カレントディレクトリ配下にディレクトリを一括作成
    // mkdir -p に相当
    if err := os.MkdirAll("foo/bar/baz", 0755); err != nil {
        log.Fatal(err)
    }
}

ディレクトリの削除

func main() {
    if err := os.Remove("NewFile.txt"); err != nil {
        log.Fatal(err)
    }

    if err := os.RemoveAll("foo") err != nil {
        log.Fatal(err)
    }
}

その他のファイル操作

シンボリックリンクの操作

func main() {
    // シンボリックリンクbar.txtを作成
    if err := os.Symlink("foo.txt", "bar.txt"); err != nil {
        fmt.Println(err)
    }

    // シンボリックリンクのリンク先を読み込む
    path, err := os.Readlink("bar.txt")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(path)
}

timeパッケージ

import "time"

現在の時刻取得

func main() {
    t := time.Now()
    fmt.Println(t)
}

指定した時刻の生成

func main() {
    t := time.Date(2019, 2, 3, 11, 50, 0, 0, time.Local)
    fmt.Println(t)
    fmt.Println(t.Year())
    fmt.Println(t.Month())
    fmt.Println(t.Day())
    fmt.Println(t.Weekday())
    fmt.Println(t.Hour())
    fmt.Println(t.Minute())
    fmt.Println(t.Second())
    fmt.Println(t.Nanosecond())
    fmt.Println(t.Zone())
}
メソッド 戻り値の型 意味
Year int
YearDay int 1~366
Month time.Month
Weekday time.Weekday 曜日
Day int 1~31
Hour int 0~23
Minute int 0~59
Second int 0~59
NanoSecond int ナノ秒
Zone string, int タイムゾーンとオフセット秒

時刻間隔の表現

func main() {
    fmt.Println(time.Hour)        // 1h0m0s
    fmt.Println(time.Minute)      // 1m0s
    fmt.Println(time.Second)      // 1s
    fmt.Println(time.Millisecond) // 1ms
    fmt.Println(time.Microsecond) // 1µs
    fmt.Println(time.Nanosecond)  // 1ns
}

文字列からtime.Durationを生成

func main() {
    // 文字列からtime.Durationを生成
    duration, err := time.ParseDuration("2h30m")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%v : %T", duration, duration) // 2h30m0s : time.Duration
}

time.Durationの計算

func main() {
    t := time.Now()
    fmt.Println(t) // 2019-02-03 12:01:38.307595 +0900 JST m=+0.000568779
    t = t.Add(2*time.Minute + 15*time.Microsecond)
    fmt.Println(t) // 2019-02-03 12:03:38.30761 +0900 JST m=+120.000583779
}

時刻の差分を取得

func main() {
    t0 := time.Date(2020, 2,1,0,0,0,0,time.Local)
    t1 := time.Now()

    d := t0.Sub(t1)
    fmt.Println(d) // 8699h55m41.548192s
}

時刻の比較

func main() {
    t0 := time.Now()
    t1 := t0.Add(24 * time.Hour)
    fmt.Println(t0) // 2019-02-03 12:09:01.320207 +0900 JST m=+0.000428654
    fmt.Println(t1) // 2019-02-04 12:09:01.320207 +0900 JST m=+86400.000428654

    // 時刻の比較
    fmt.Println(t1.Before(t0)) // false
    fmt.Println(t1.After(t0))  // true
}

年月日の増減

func main() {
    t0 := time.Date(2019,2,1,0,0,0,0,time.Local)
    fmt.Println(t0) // 2019-02-01 00:00:00 +0900 JST

    // 1年増やす
    t1 := t0.AddDate(1, 0,0)
    fmt.Println(t1) // 2020-02-01 00:00:00 +0900 JST

    // 1ヶ月減らす
    t2 := t0.AddDate(0,-1,0)
    fmt.Println(t2) // 2019-01-01 00:00:00 +0900 JST
}

文字列からの時刻生成

func main() {
    // 第一引数にフォーマットを指定
    // 第二引数がパース対象の文字列
    t, err := time.Parse(time.RFC822, "02 Jan 06 15:04 MST")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(t) // 2006-01-02 15:04:00 +0000 MST
}

第一引数のフォーマットは以下の通り定義されている。

const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"
)

自分でフォーマットを定義する場合は、例えば以下のようにする。
注意することとして、フォーマット指定のタイムスタンプは2006年1月2日 15:04:05で統一されていないといけない。

このフォーマット指定に疑問を抱いたので調べたところ、アメリカの時刻の順番らしい。”1月2日午後3時4分5秒2006年”でとなっていて、1,2,3,4,5,6と並んでいる。

func main() {
    t, err := time.Parse("2006年1月2日 15時04分05秒", "2019年2月3日 21時15分00秒")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(t) // 2019-02-03 21:15:00 +0000 UTC
}

時刻からの文字列生成

func main() {
    t := time.Now()
    f1 := t.Format(time.RFC822)
    f2 := t.Format(time.RFC3339)
    f3 := t.Format("2006年1月2日 15時43分28秒")
    f4 := t.Format("2006/01/02")

    fmt.Printf("f1: %v (%T)\n", f1, f1) // f1: 03 Feb 19 14:05 JST (string)
    fmt.Printf("f2: %v (%T)\n", f2, f2) // f2: 2019-02-03T14:05:05+09:00 (string)
    fmt.Printf("f3: %v (%T)\n", f3, f3) // f3: 2019年2月3日 14時52分38秒 (string)
    fmt.Printf("f4: %v (%T)\n", f4, f4) // f4: 2019/02/03 (string)
}

時刻のUTC変換

func main() {
    t := time.Now()
    utc := t.UTC() // UTCに変換
    fmt.Printf("utc : %v (%T)\n", utc, utc) // utc : 2019-02-03 05:07:07.324682 +0000 UTC (time.Time)
}

時刻のローカルタイム変換

func main() {
    t := time.Now()
    jst := t.Local() // ローカルタイムに変換
    fmt.Printf("jst : %v (%T)\n", jst, jst) // jst : 2019-02-03 14:08:24.109525 +0900 JST (time.Time)
}

UNIX時間との相互変換

func main() {
    t := time.Now()
    unix := t.Unix()
    fmt.Printf("unix : %v (%T)\n", unix, unix) // unix : 1549170553 (int64)
}

指定時間のスリープ

func main() {
    for i := 0; i < 10; i ++ {
        fmt.Println(i)
        time.Sleep(100 * time.Millisecond) // 100msecスリープ
    }
}

time.Tick

time.Tickは、指定した時間間隔ごとに現在時刻を表すtime.Time型の値が送信されるチャネルを生成する。

func main() {
    // 3秒間隔で現在の時刻を送信するチャネルを定義
    ch := time.Tick(3 * time.Second)

    // 無限ループ
    for {
        t := <-ch
        fmt.Println(t) // 3秒間隔で表示される
    }
}

time.After

time.Afterは、チャネルに対して指定した時間間隔後に一度だけ現在時刻を表すtime.Timeを送信する。

func main() {
    // 5秒後に時刻を送信するチャネル
    ch := time.After(5 * time.Second)

    // 5秒後に表示される
    v := <-ch
    fmt.Println(v)
}

A Tour of Goのサンプルではtime.Ticktime.Afterを組み合わせてselect-for文で以下のような使い方をしていた。

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick: // tickチャネルから受信したの場合
            fmt.Println("tick.")
        case <-boom: // boomチャネルから受信した場合はreturnでfor文から抜ける。
            fmt.Println("BOOM!")
            return
        default: // デフォルトの場合は50msecスリープ
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

今週は週3回のクソ記事投稿目標をなんとか達成。。

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

PerlとGolangの対応表っぽいもの

「PerlではこうだけどGolangだとこう書く」のリストです。
これまでPerlを使ってきて、Golangに入門した方向け。すごくニッチです。

スクリプトとコンパイル言語、動的型付けと静的型付けと全く違う言語であり本来比較にならないかと思います。
しかし基礎構文レベルの細かいことでPerlだとこうだけどGolangだとどうやるんだろ?なんて調べることが多く、自分の勉強がてらまとめてみました。

公式リファレンス

手元確認バージョン

表の項目は細かい方に合わせています。数値と文字列で比較演算子が異なるPerlや、データ型が多いGolangなど。

コメント

意味 Perl Golang
単行 # コメントですよ // コメントかな?
複数行 =pod
ドキュメントなんかで
使います
=cut
/*
こっちのが
みなれてるかも
*/

算術演算子

意味 Perl Golang
足す $a + $b a + b
引く $a - $b a - b
掛ける $a * $b a * b
割る $a / $b a / b
余りを求める $a % $b a % b
インクリメント $a++ a++
デクリメント $a-- a--
文字列結合 $a . $b a + b

ほぼ一緒。

比較演算子

意味 Perl Golang
等価 数値 $a == $b a == b
等価 文字列 $a eq $b a == b
不等価 数値 $a != $b a != b
不等価 文字列 $a ne $b a != b
小なり $a < $b a < b
以下 $a <= $b a <= b
大なり $a > $b a > b
以上 $a >= $b a >= b

データ型

意味 Perl Golang
真偽値 真=1、偽=0
#perlに真偽値は無い
真=true、偽=false
整数 符号なし8bit my $a = 255 var a uint8 = 255
var a byte = 'A' //65
整数 符号なし16bit my $a = 65535 var a uint16 = 65535
整数 符号なし32bit my $a = 4294967295 var a uint32 = 4294967295
整数 符号なし64bit use bigint;
my $a = 18446744073709551615
var a uint64 = 18446744073709551615
整数 符号あり8bit my $a = -128 var a int8 = -128
整数 符号あり16bit my $a = -32768 var a int16 = -32768
整数 符号あり32bit my $a = -2147483648 var a int32 = -2147483648
var a rune = 'あ' //12354
整数 符号あり64bit use bigint;
my $a = -9223372036854775808
var a int64 = -9223372036854775808
浮動少数 32bit my $a = 3.14 var a float32 = 3.14
浮動少数 64bit my $a = 0.000314159265358979 var a float64 = 0.000314159265358979
文字列 my $a = "hoge" var a string = "hoge"
配列 my @a = (1, 2) var a [2]int = [2]int{1, 2}
//配列は桁固定
スライス my @a = (1, 2) var a []int = []int{1, 2}
//スライスは可変
ハッシュ/マップ my %a = ("hoge" => 1, "fuga" => 2) var a map[string]int = map[string]int{"hoge": 1, "fuga": 2}
  • Perlの数値はおおよそ15桁までで、限界値はOSのライブラリに準じるとの事。それ以上はbigintモジュールを入れます。
  • Golangの配列は値で、スライスは配列へのポインタ(+サイズなどの情報を持つ構造体)です。配列はメモリ使用効率に優れる点で、スライスと使い分けされます。
  • Golangのbyteとruneは数値型のエイリアスですが慣例的に文字コード(Perlで言うord結果)として使われ、例えば'A'で65が、'あ'で12354が入力されます。文字列として扱うには後述の型変換が必要です。
  • 後半Golangの宣言がやたら長いですが、実際はvarを省略したスコープ限定の型推論による代入(:=)を使うことが多いです。

文字列フォーマット

意味 Perl Golang
- %T
文字 %s %s
文字 スペース埋め %2s %2s
2進数 %b %b
8進数 %o %o
10進数 %d %d
10進数 少数 %f %f
10進数 0埋め %02d %02d
16進数 小文字 %x %x
16進数 大文字 %X %X
ポインタアドレス %p %p

元がCなので、一緒ですね。

printf '%02d', 1;
> 01
import "fmt"
fmt.Printf("%02d", 1)
> 01

ヒアドキュメント(複数行の文字列)

Perl

my $a = <<__QUERY;
SELECT hoge
FROM hage;
__QUERY

任意の識別子から識別子まで。(上記例は"__QUERY")

Golang

a := `SELECT hoge
FROM hage;`

バッククォート(`)で囲む。

リファレンス/ポインタ

意味 Perl Golang
無名のスカラ変数のアドレス my $a = \'hoge'
my $a = \1
-
無名の配列のアドレス my $a = [1, 2] //スライス = 配列のポインタ
var a []int = []int{1, 2}
無名のハッシュのアドレス my $a = {hoge => 1, fuga => 2} //マップは参照型
var a map[string]int = map[string]int{"hoge": 1, "fuga": 2}
無名の関数のアドレス my $a = sub {
my ($in) = @_; print $in
};
&$a('hoge');
var a func(string) = func(in string) {
fmt.Print(in)
}
a("hoge")
スカラ変数のアドレス my $a = \$b
# "\"でアドレス取得
var a *int = &b
// "&"でアドレス取得
配列のアドレス my $a = \@b //bはスライス
var a []int = b
//bは配列
var a *[2]int = &b
ハッシュ/マップのアドレス my $a = \%b var a map[string]int = b
スカラ変数のデリファレンス print $$a fmt.Print(*a)
配列のデリファレンス print $$a[0]
print $a->[0]
#どちらでも良い
fmt.Print(a[0])
//配列のポインタはデリファレンスの必要なし
  • Golangのスライスとマップはもともと参照型です。interfaceという多態性の為の仕組みがあり、Print時にStringerインターフェイスが実装されていれば実行されます。なのでPerlのように、printしたらアドレスが見れるわけではないです。
  • Golangの配列のポインタはスライスかと思ったんですが、あくまで配列のポインタのようです。(配列は固定サイズなのでappendするとエラーになる)

制御構造

条件分岐 if

Perl

# if文
if ($a == 0) {
  print "0だよ";
} elsif ($a == 1) {
  print "1だよ";
} else {
  print "なんだこれ";
}
# 三項演算子
print ($a == 0) ? "0だよ" : "なんだこれ";

Golang

if a == 0 {
  fmt.Print("0だよ")
} else if a == 1 {
  fmt.Print("1だよ")
} else {
  fmt.Print("なんだこれ")
}
// スコープ限定の変数定義
if err := SelectData(); err != nil {
  fmt.Print("あかん")
}
  • Golangは括弧が不要です。また、スコープ限定の変数が使えます。
  • Golangに三項演算子はありません。

条件分岐 switch

Perl

given ($a) {
  when ($_ == 1) {
    print "1だよ";
  }
  default {
    print "なんだこれ";
  }
}
  • Perlには元々switchはありません。
    新機能のgivenは「実験的機能」とされています。私は一度も使ったことがありません。

Golang

// aを判定
switch a {
  case 1:
    fmt.Print("1だよ")
  default:
    fmt.Print("なんだこれ")
}
// 各条件内で判定
switch {
  case a == 1:
    fmt.Print("1だよ")
  default:
    fmt.Print("なんだこれ")
}
  • Golangのswitchはデフォルトでbreakします。
    逆に以降の条件も判定したいときは、fallthroughと書きます。
    また、いわゆるswitch trueな使い方が正式に実装されています。

ループ for

Perl

for (my $i=0; $i<$a; $i++) {
  print "$iです";
}

Golang

for i:=0; i<a; i++ {
  fmt.Printf("%dです", i)
}

特に言うことなし。

ループ while

Perl

# 条件に合う限りループ
while($a > 1) {
  print "やばいよループしてますよー";
  $a--;
}
# 無限ループ
while(1) {
  print "∞";
}

Golang

// 条件に合う限りループ
for ;i<a; {
  fmt.Printf("%dです", i)
}
// 無限ループ
for {
  fmt.Print("∞")
}
  • Golangのループの制御構造は全てforで書きます。
  • Golangのforは「初期化; 条件判定; 後処理」の全てが任意に省略できます。
    条件判定を省略すれば無限ループになります。

イテレータ 配列

Perl

@values = ("a", "b", "c");
foreach my $v (@values) {
  print $v;
}
> a, b, c

Golang

values := []string{"a", "b", "c"}
for i, v := range values {
  fmt.Printf("%d->%s, ", i, v)
}
> 0->a, 1->b, 2->c,

イテレータ ハッシュ/マップ

Perl

%hash = (a=>1, b=>2, c=>3);
foreach my $k (keys %hash) {
  print "$k\->$hash{$k}, ";
}
> c->3, a->1, b->2,
# ハッシュのキー順序は不定

Golang

values := map[string]int{"a": 1, "b": 2, "c": 3}
for i, v := range values {
  fmt.Printf("%s->%d, ", i, v)
}
> a->1, b->2, c->3,
// マップのキー順序も不定

型変換

Perl
Perlの場合、スカラ値は強いて言えば文字列か数値かの違いしかありません。

# 文字列を示す「"」で囲っても、数値計算できる
my $a = "3.14";
print $a * 3;
> 9.42
# JSONに渡す時などにたまに使うint
my $int = int($str);
# 「"」で囲めば文字列扱いになる
my $str = "$int";

Golang
数値型については、型(値)で変換可能です。

a := 1.15
fmt.Printf("%T, %d", int(a), int(a))
> int, 1
b := int(a)
fmt.Printf("%T, %f", float64(b), float64(b))
> float64, 1.000000

文字列⇔数値はstrconvパッケージを使います。
https://golang.org/pkg/strconv/

import "strconv"
// 文字列→数値
i, err := strconv.Atoi("-42")
// 数値→文字列
s := strconv.Itoa(128)

後述しますが以下は数値がアスキーコードと見なされてしまい、
Perlで言うchrの挙動になってしまいますので
「数値をそのまま文字列として表示したい」場合には使えません。

i := 65
fmt.Println(string(i))
> A

文字⇔アスキーコード変換

Perl

# 文字→コード
print ord('A');
> 65

# コード→文字
print chr(65);
> A

print chr(0x3042);
> 

Golang
Golangのここが私にとっては特殊(初見)でした。
byteとruneという数値型のエイリアスがあり、文字をシングルクォート「'」で囲むとbyteまたはruneとして、ダブルクォート「"」で囲むと文字列として認識されます。
個人的に最初にここでちょっと詰まり、こういうまとめを作ろうと思ったきっかけでもあります。

// この場合byte型
a := 'A'
fmt.Print(a)
> 65

// この場合string型
b := "B"
fmt.Print(b)
> B

// stringは内部的にはbyte(rune)の配列
fmt.Print(b[0])
> 66

// 文字→コード
c := "あいうえお"
for _, v := range c {
  fmt.Printf("%x,", v)
}
> 3042,3044,3046,3048,304a,

// コード→文字
d := 65
fmt.Print(string(d))
> A

f := 0x3042
fmt.Print(string(f))
> 

全然足りない気がするけど、とりあえずここまで。

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