woshidan's loose leaf

ぼんやり勉強しています

今週のGo

週5以上活動していればよい感じで。

  • 以下のようなコードを動かしながら、関数値gの中でその値が定義された関数f内の変数Aを利用すると、関数値gは変数Aの参照を持っているのだなぁ、という確認をしていた
  var once sync.Once

  onceFunc := func() {
    once.Do(func() { fmt.Println("A") })
  }

  func main() {
    var once sync.Once

    firstFunc := func() {
      once.Do(func() { fmt.Println("A") })
    }

    secondFunc := func() {
      once.Do(func() { fmt.Println("A") })
    }

    firstFunc() // 1回しか実行されない
    secondFunc()
  }

  // $ go run sync.go 
  // A
  func main() {
    var cnt int

    firstFunc := func() {
      cnt++
      fmt.Println("A: ", cnt)
    }

    secondFunc := func() {
      cnt++
      fmt.Println("B: ", cnt)
    }

    firstFunc()
    secondFunc()
  }

  // $ go run sync.go 
  // A:  1
  // B:  2 // それぞれの関数値で値を定義した時点でのコピーを持っているなら、ここは1のはず
  • 定数を見てて知らない概念がいてひよる
    • TLSDialTimeout TLSDialTimeout = 20 * time.Second
      • TLSにDialという概念がある..?
      • 通信が完了するまで待つ時間
    • HTTPClientTimeout HTTPClientTimeout = 60 * time.Second
      • 個々の通信自体の時間 + リダイレクト + リクエスト・レスポンスの読み書き合計したタイムアウト
    • TCPKeepAlive = 60 * time.Second
      • ネットワーク接続のキープアライブの時間
      • キープアライブ自信ねえ...
  • コメント読むの好き
    • // We add : because we include :port as part of host. のように一つずつ値について説明してあるやつとか
  • 値を直接入力させず、値を設定するメソッドを置いている
  // 値を直接入力させず、値を設定するメソッドを置いている
  // Development sets the Client to use the APNs development push endpoint.
  func (c *Client) Development() *Client {
    c.Host = HostDevelopment
    return c
  }

  // Production sets the Client to use the APNs production push endpoint.
  func (c *Client) Production() *Client {
    c.Host = HostProduction
    return c
  }
  • golintのusage が結構愚直に書いてあった
  func usage() {
    fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
    fmt.Fprintf(os.Stderr, "\tgolint [flags] # runs on package in current directory\n")
    fmt.Fprintf(os.Stderr, "\tgolint [flags] [packages]\n")
    fmt.Fprintf(os.Stderr, "\tgolint [flags] [directories] # where a '/...' suffix includes all sub-directories\n")
    fmt.Fprintf(os.Stderr, "\tgolint [flags] [files] # all must belong to a single package\n")
    fmt.Fprintf(os.Stderr, "Flags:\n")
    flag.PrintDefaults()
  }
  • 分岐について自分個人は早期returnしたくなるところで if { 短い } else { 長い }
  func main() {
    flag.Usage = usage
    flag.Parse()

    if flag.NArg() == 0 {
      lintDir(".")
    } else {
    uerr := func(err error) error {
      // the body may have been closed already by c.send()
      if !reqBodyClosed {
        req.closeBody()
      }
      method := valueOrDefault(reqs[0].Method, "GET")
      var urlStr string
      if resp != nil && resp.Request != nil {
        urlStr = resp.Request.URL.String()
      } else {
        urlStr = req.URL.String()
      }
      return &url.Error{
        Op:  method[:1] + strings.ToLower(method[1:]),
        URL: urlStr,
        Err: err,
      }
    }
  • switchに式を与えず case に 式を書くのも普通にやるようだ
  func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
      return c - '0'
    case 'a' <= c && c <= 'f':
      return c - 'a' + 10
    case 'A' <= c && c <= 'F':
      return c - 'A' + 10
    }
    return 0
  }

今週のGo

  • defer FILO/panicになっても実行される
  • recoverはpanicが起こる関数と同じgoroutineの中に置く
  • deferと対になる処理について
  • errorに判定を生やす標準ライブラリの実装例

ログにもありますが、試したGoのバージョンは1.7.3です

deferはFILOでpanicになっても実行される

package main

import "fmt"

func main() {
  defer func() { fmt.Println("1st in") }()
  defer func() { fmt.Println("2nd in") }()

  arr := []int{1, 2, 3}
  fmt.Printf("%v", arr[100]) // raise panic
}
2nd in
1st in
panic: runtime error: index out of range

goroutine 1 [running]:
panic(0x8efc0, 0xc42000a0d0)
  /usr/local/Cellar/go/1.7.3/libexec/src/runtime/panic.go:500 +0x1a1
main.main()
  /Users/yoshidanozomi/study/gopl/study/defer.go:10 +0x93
exit status 2

panicをrecoverで拾えるスコープはgoroutineの中

panicの情報をrecoverで拾える範囲がよくわかってなかったのですが、同じgoroutineで実行される範囲のようです。

package main

import "fmt"

func main() {
  defer func() {
  if err := recover(); err != nil {
        fmt.Println("err:", err)
    }
  }()
  panic("test")
}
err: test
package main

import "fmt"
import "sync"

func main() {
  var wg sync.WaitGroup
  wg.Add(1)

  go func () {
    defer wg.Done()

    // recoverはdeferの中でしか実行できない
    // 同じgoroutineの中であれば、
    // recoverを書くのはpanicが起こる関数の中でなくてもよい
    defer func() {
    if err := recover(); err != nil {
          fmt.Println("err:", err)
      }
    }()
    recoverTest()
  }()

  wg.Wait()
}

func recoverTest() {
  panic("test")
}
err: test
package main

import "fmt"
import "sync"

func main() {
  var wg sync.WaitGroup
  wg.Add(1)

  // deferをpanicが起こるgoroutineの外に置いた場合
  defer func() {
  if err := recover(); err != nil {
        fmt.Println("err:", err)
    }
  }()

  go func () {
    defer wg.Done()
    recoverTest()
  }()

  wg.Wait()
}

func recoverTest() {
  panic("test")
}
$ go run defer.go 
panic: test

goroutine 5 [running]:
panic(0x89e00, 0xc42000a2c0)
  /usr/local/Cellar/go/1.7.3/libexec/src/runtime/panic.go:500 +0x1a1
main.recoverTest()
  /Users/woshidan/study/gopl/study/defer.go:28 +0x6d
main.main.func2(0xc42000a2b0)
  /Users/woshidan/study/gopl/study/defer.go:21 +0x4a
created by main.main
  /Users/woshidan/study/gopl/study/defer.go:22 +0x9a
exit status 2

deferと対になる処理について

go fmt で整形してくれるわけではないですが、Go Blog Defer, Panic, and Recoverあたりを見るに*1

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

のように、後始末が行われてほしい処理の近くにおくのがよいかもです。

errorに判定を生やす標準ライブラリの実装例

net/http ライブラリの実装例 :eyes: .

https://golang.org/src/net/net.go?h=Temporary#L375

// An Error represents a network error.
type Error interface {
  error
  Timeout() bool   // Is the error a timeout?
  Temporary() bool // Is the error temporary?
}

Go、割と雑に一つのファイルに長く処理が書いてあったりして、復習してる間にhttpのコード眺めててひやっとしたり、いままで違う種類のプログラミングなので迷わしい。。なので慣れるまでもうちょっと読む量増やしてこ、ということで現場からは以上です。

*1:まあ、古いですがががが

dispatch_queue_tの_tとはなんなのか

type の略。

stackoverflow.com

現場からは以上です。

Intentのフラグを使ってスタックの下の方のActivityをクリアする

下記のフラグをつけてstartActivityする。

Intent intent = new Intent(ThirdActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);

サンプルコード https://gist.github.com/woshidan/72bafcbcfabbd4c91a1ee092e36c3a9f

参考

https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TASK

iOS9.0のシミュレータでローカルホストへ接続した時、NSPOSIXErrorDomainになる

開発用にダミーサーバへ接続したらやたらエラーになるので調べたところ、iOS9.0でシミュレータでlocalhostへ接続した場合だけに下記のエラーが出るみたいでした。

Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" UserInfo={NSErrorFailingURLStringKey=http://192.168.10.5:8080/test, NSErrorFailingURLKey=http://192.168.10.5:8080/test, _kCFStreamErrorCodeKey=2, _kCFStreamErrorDomainKey=1}

関連すると思しきStackOverflowのページは以下。

stackoverflow.com

上記のエラーが発生している場合、リモートのホストと通信した場合はエラーになりません。エラーが発生するのを確認した条件だけメモします。

テスト時に利用していたXCodeは諸事情によりXCode 8.3です。

  • iPhone6s iOS 9.0 to remote host => エラーにならない
  • iPhone6s iOS 9.0 to local host => エラー
  • iPhone6s iOS 10.0 to local host => エラーにならない
  • iPhone6 iOS 9.0 to local host => エラー
  • iPhone6 iOS 9.3 to local host => エラーにならない
  • iPhone5s iOS 9.0 to local host => エラーになる
  • iPhone5 iOS 9.0 to local host => エラーになる

現場からは以上です。

bashで引数の配列の一つ後の要素を取り出す

即席で作ったシェルスクリプトのオプションの自前解析がしたくて。。

#!/bin/bash

i=0
for OPT in "$@"
do
    echo "index: $i OPT: $OPT OPTの一個後ろの要素 ${@:$(expr $i + 2):1}" # $i + 1 でないのは@の先頭は全要素を出力するため
    i=$(expr $i + 1)
done
$ test.sh aa bbb c d
index: 0 OPT: aa OPTの一個後ろの要素 bbb
index: 1 OPT: bbb OPTの一個後ろの要素 c
index: 2 OPT: c OPTの一個後ろの要素 d
index: 3 OPT: d OPTの一個後ろの要素 

http://d.hatena.ne.jp/y-kawaz/20100720/1279607344

Athenaのテーブル定義にファイル中に存在しない列を定義した場合の振る舞いについて

後からAthenaのテーブル定義に列を追加したい、みたいな場合に気になったので、メモ。

テーブル定義に存在する列で検索対象データ内で型が違うものは怒られますが、テーブル定義に存在して検索対象のデータに存在しない列についてはNULL扱いみたいでした。

以下試したクエリ。

CREATE EXTERNAL TABLE IF NOT EXISTS logs_with_absent (
  `id` String,
  absent_str String, // 存在しない列
  absent_int int     // 存在しない列
  ) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' LOCATION 's3://woshidan-exmaple-test/athena_empty_key_test';
Query successful. 
SELECT COUNT(*) FROM "mydatabase"."logs_with_absent" WHERE "logs_with_absent"."absent_str" IS NULL limit 10 ;
=> 1
SELECT COUNT(*) FROM "mydatabase"."logs_with_absent" WHERE "logs_with_absent"."absent_int" = 0 limit 10 ;
=> 0
SELECT COUNT(*) FROM "mydatabase"."logs_with_absent" WHERE "logs_with_absent"."absent_int" IS NULL limit 10 ;
=> 1