来世から頑張る!!

技術ブログを目指して

Scalaでの🍣の数え方

最近のおっさんはビールもきちんと数えられないことに驚愕したので、ScalaですがJavaの話をします。 昔と違ってUTF-8の半角カナが3バイトだと信じてくれないおじさんとは出会わなくなってきたなと思ってたのに、世の中そう甘くはなかったようです。

Javaの話なのでClojureだろうがKotlinだろうがJasminだろうが基本的におんなじです。

文字の数え方

🍺🍺🍺 <- これ、いくつかと聞かれたら3と答えて欲しいわけですよ!

scala> val beers = "🍺🍺🍺"
beers: String = 🍺🍺🍺

scala> print(beers.length)
6

当然、emojiなのでサロゲートペアですよ。 codePointCountを使ってくださいねという話です。

scala> print(beers.codePointCount(0, beers.length))
3

せっかくなのでScalaらしくpimpしちゃいましょう。 メソッド名はStringOpsとかとかぶらないように・・・

implicit class StringCount(val s: String) extends AnyVal {
  def letterCount: Int = s.codePointCount(0, s.length)
}

これでbeers.letterCountで文字数が出せるわけですね!

合成文字にも対応しておく

せっかくなので合成文字の数え方にも対応しておきましょう。
参考: Java SE 6 バラバラにして組み立てて - Normalizer

Java(というかUnicode)の正規化には2種類(2段階)あって、Å("\u0041\u030a")のような合成文字をÅ("\u00c5")に圧縮するだけの正規化Canonical Composeと、同じ意味を表すもっと(だいたい英語圏民に)一般的そうな文字に置き換えるCompatibility Composeがあります。

前者に使うのがjava.text.Normalizer.Form.NFC、後者に使うのがjava.text.Normalizer.Form.NFKCです。

試してみましょう。

scala> val a = "\u0041\u030a"
a: String = Å

scala> val b = "\u00c5"
b: String = Å

scala> a == b
res0: Boolean = false

scala> val c = Normalizer.normalize(a, Normalizer.Form.NFC)
c: String = Å

scala> b == c
res1: Boolean = true

scala> val d = "㌔㍉"
d: String = ㌔㍉

scala> val e = Normalizer.normalize(d, Normalizer.Form.NFC)
e: String = ㌔㍉

scala> val f = Normalizer.normalize(d, Normalizer.Form.NFKC)
f: String = キロミリ

文字数を数えたいので、使うのはNFCですね。

import java.text.Normalizer
implicit class StringCount(val s: String) extends AnyVal {
  def normalized: String = Normalizer.normalize(s, Normalizer.Form.NFC)
  def letterCount: Int = {
    val n = this.normlized
    n.codePointCount(0, n.length)
  }
}
scala> val x = "🍺\u0041\u030a🍣\u0041\u030a🍺"
x: String = 🍺Å🍣Å🍺

scala> x.length
res0: Int = 10

scala> x.letterCount
res1: Int = 5

寿司が2貫で1個なのかはともかく、酔っぱらっても1つのビールが2つに見えるようにはなりたくないものです。

一般的なShellで空白を含むPathをループしたい願望

まず、Shellって何かがわからないところから入るわけだけど、 いや、自分が打っているのが所謂shなのかbashなのかzshなのかがわからないってことなんだけど、 まあ複数ファイルを順次処理したい時ってあるわけですね。

forを使ったループ

シンプルな例だと以下のように書くわけです。

  for f in path/to/target/*.txt
  do
    echo "file is: $f"
  done

このディレクトリーにa.txt, b.txt, c.txtがあれば、以下のように表示されるでしょう。

file is: path/to/target/a.txt
file is: path/to/target/b.txt
file is: path/to/target/c.txt

スペース(など)が含まれたパス

ところが、スペースが含まれているようなディレクトリー名だと、これがうまく動かないのです。

ディレクトリー名をpath/to target with space/だとしてみましょう。

ls "path/to target with space/"
a a.csv      b c d.csv

こんな場合ですね。

同じようなループを実行すると、当然うまく行きません。

for f in path/to target with space/*.csv
do
  echo "file is: $f"
done

結果はこんな感じになったりしてしまいます。

file is: path/to
file is: target
file is: with
file is: space/*.csv

解決方法1: IFS組み込み変数を使用

どうにかスペース入のパスをループできないのかなーと検索した結果見つけたのが、 組み込み変数IFSfindコマンドを組み合わせる方法です。

IFSを変更すると、shellが扱う区切り文字を変更できるとこのことです。

まずfindを実行すると、以下のような結果が取得できます。

> find "path/to target with space" -type f -name "*.csv"
path/to target with space/a a.csv
path/to target with space/b c d.csv

結果が改行されて出てくるので、IFSに改行記号\nを設定後ループするとうまくいくようです。
ただし、IFSを元に戻せるように、退避してから実行した方が良いようです。

IFS_BACK="$IFS"
IFS=$'\n'
for f in `find "path/to target with space" -type f -name "*.csv"`
do
  echo "file is: $f"
done
IFS="$IFS_BACK"
file is: path/to target with space/a a.csv
file is: path/to target with space/b c d.csv

解決方法2: ""でくくる

とりあえず上をコピペして使っていたのですが、 今日もっと簡単にできる方法を発見してしまったのでそちらに書き換えることにしました。

皆さんご存知のように、shellではダブルクォートに囲まれた部分は1つとみなしてくれるのです。
ただし、中にある*は展開してくれません。

まず、ダメな例。

for f in "path/to target with space/*.csv"
do
  echo "file is: $f"
done

これの結果は以下のようになります。

file is: path/to target with space/*.csv

*がそのままですね。

すごく単純すぎて気づかなかったのですが、 *をダブルクォートの外に出してあげれば展開されるんですよね、これ。

for f in "path/to target with space/"*".csv"
do
  echo "file is: $f"
done
file is: path/to target with space/a a.csv
file is: path/to target with space/b c d.csv

どこでダブルクォートを終わらせてもいいみたいなので、変数の部分だけくくるとかが現実的なのかなという感じです。

BASE_PATH="path/to target with space"
for f in "$BASE_PATH"/*.csv
do
  echo "file is: $f"
done

結論

shell難しい。

自分のshell力の低さを痛感させられた。

自分用メモ #scala

アレ用のアレ。

implicit def fs2effectTask: fs2.util.Effect[scalaz.concurrent.Task] = {
  import scalaz.concurrent.Task
  import fs2.util.Effect
  
  new Effect[Task] {
    def fail[A](err: Throwable) = Task.fail(err)
    def attempt[A](t: Task[A]) = t.attempt.map(_.toEither)
    def pure[A](a: A) = Task.now(a)
    def bind[A,B](a: Task[A])(f: A => Task[B]): Task[B] = a flatMap f
    override def delay[A](a: => A) = Task.delay(a)
    def suspend[A](fa: => Task[A]) = Task.suspend(fa)

    override def toString = "fs2.util.Effect[scalaz.concurrent.Task]"
  }
}

SQL99のWITH句について

MySQLしか触った事のない人にとっては全く知らない文法だというとこを最近知ったので。

WITH句とは

WITH句は、ネストされたテーブルに別名をつけて読みやすくするために使用する構文。

PostgreSQL, Oracle, MS SQL Serverなどで使用可能。 MySQLは(少なくとも少し前のバージョンでは)対応していない。

-- ネストされたテーブル
SELECT
    *
FROM
    (
        SELECT
            user_id,
            SUM(payment) AS total_payment
        FROM
            payments
        GROUP BY
            user_id
    ) a
WHERE
    a.total_payment > 3000

この例だとHAVINGすればいいとかはひとまず無視して、WITHを使うとこんな感じ。

WITH a AS (
    SELECT
        user_id,
        SUM(payment) AS total_payment
    FROM
        payments
    GROUP BY
        user_id
)

SELECT
    *
FROM
    a
WHERE
    a.total_payment > 3000

通常の言語の変数宣言と同じ!!便利!!! 変数のように、適切な名前をつけるべき。aとかbとかfとか。

複数の変数を使用する

WITH句で複数のテーブルを取得する場合、,で繋げる。

WITH a AS (SELECT ...),
b AS (SELECT ...),
c AS (SELECT ...)

SELECT * FROM c

自分より上で宣言された別名は利用可能!!

WITH a AS (
    SELECT
        user_id,
        MAX(log_date) AS log_date
    FROM
        some_log
    GROUP BY
        user_id
),
b AS (
    SELECT
        *
    FROM
        some_log s
    WHERE
        EXISTS(
            SELECT
                0
            FROM
                a
            WHERE
                a.user_id = s.user_id
                AND
                a.log_date = s.log_date
        )
),

...

再帰

一部条件を満たせば、自分自身の中でも再帰的に参照が可能。

WITH a AS (
    SELECT 0 AS index
    UNION
    SELECT index + 1 AS index FROM a WHERE index < 2
)
SELECT * FROM a
  • PostgreSQLではWITH RECURSIVE a ASとしないと不可。他のDBは知らない。
    つまり1番目の変数のみ再帰可能。

関数型の再帰と同様に、ループの終了条件書かないと無限ループして返ってこないので注意。

結果はこんな感じ。

index
=====
0
1
2
3

これを利用すれば「n月m日からo日までの毎日、0件なら0を取得する」なども簡単ですよね?

パフォーマンスについて

WITH句で別名をつけた場合、1つの名前につき最大1回しか読み込まれないことが保証される。 また、ほとんどのDBMSではSQLを最後まで読み込んだうえで最適化するので、インデックススキャンで済むものは一時テーブルを作成しない。 なのでネストした表現と最低限同等、読み込み回数によってはWITHの方が圧倒的に効率化が可能。

でも、最後に信じるべきはオプティマイザーですね。過信ダメ、ゼッタイ。

あ、MySQLではCREATE TEMPORARY TABLE 〜 SELECT 〜を使おう。
こっちは自分でインデックス貼れるのでもっと早いよ!!!

AnsibleでDockerとかしたいのにdocker-pyに苦しめられた話

はい。タイトル通りです。

ansibleとはpythonで動いてるけどpython書かなくてもいい不思議な何かという認識。

で、ansibleからdockerコンテナー立ち上げるときのレシピがこちら。

---
- name: Run CentOS container
  docker:
    image: "centos:centos6"
    name: sample001
    tty: yes
    net: host
    docker_api_version: 1.18

net=hostはネット設定はデフォルト不可だよというエラーメッセージ用、 docker_api_version=1.18は本番で動いてるdockerのバージョン違うよ用です。

なのですが、これを実行してもdocker-pyが見つからないよ系エラーが出るのです。

ということでローカルでpip install docker-pyしたりしてみたのですが、 これが一向に消えてなくならない。

virtualenvがダメなのかもとか悩んでいろいろググったり試したりした結果・・・

docker-pyは設定先の環境のデフォルトpythonに必要でした!!

はい。ローカルに入っていようが関係なかったのです。 よって、必要なのは設定先ホストでのpip install

これもansibleに書いてしまいます。

---
- name: Install docker-py
  become: yes
  become_method: sudo
  become_user: root
  pip:
    name: "docker-py"
    version: 1.7.1
    state: present

ansibleの恒等性とかを考えると、state=latestとどっちがいいのかわかりません。

sudoはたぶん必要。と信じている。

このためにはサーバーにもpipが入っていないといけないので、 それはサーバーのディストリビューションに合わせてって感じになるのかと。

私からは以上です。

GolangでShift_JIS(Windows31J)のファイルを読み込み

たどり着くまで時間がかかったのでメモ。

文字コードを意識しないファイルの読み込み

つまりはUTF-8のファイル。BOMはどうなんだろう?(試していない)

os.Openbufio.NewReaderで行ごとに読み込める。

注意点: 読み込み完了時はerrorio.EOFが入る。

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    path := "/path/to/your/file"
    fp, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    reader := bufio.NewReader(decoder.Reader(fp))
    for {
        line, _, err := reader.ReadLine()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        fmt.Println(string(line))
    }
}

文字コードを考慮する。

とりあえずgolang decodeとかでググってみるとencodingパッケージが出てくる。

どうやらDecoder.ReaderでラップしたReaderが作れそう。

じゃあDecoderってどうやって作るの?と探したところ、japaneseパッケージShiftJISを発見。 こいつがencoding.Encoding型なので、NewDecoderメソッドを持っているらしい。

とりあえずjapaneseパッケージをゲット

go get golang.org/x/text/encoding/japanese

package main

import (
    "bufio"
    "fmt"
    "golang.org/x/text/encoding/japanese"
    "io"
    "os"
)

func main() {
    path := "/path/to/your/file"
    fp, err := os.Open(path)
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    decoder := japanese.ShiftJIS.NewDecoder()
    reader := bufio.NewReader(decoder.Reader(fp))
    for {
        line, _, err := reader.ReadLine()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        fmt.Println(string(line))
    }
}

bufio.NewReaderに変換する前にデコードをはさめばOK
わかれば簡単。

ScalaでゆるふあにDB接続


この記事はScala Advent Calendar 2015 12日目です。

www.adventar.org

昨日は同じく私による今日から始めるScalaプログラミングでした。

前置き

私はアプリケーションにSQLを自分で書きたい派です。
参考: ScalaでSQLが書きたいんだ!!

なので積極的に自分で書けるライブラリーが好きです。

  1. DBに直接繋いで、取得結果や処理速度などを調整するクエリーを書く。
  2. 出来上がったクエリーを作成するためのScalaコードを書く。

こんな風に作業したいのです。

doobie or not DB

ということでdoobieです。

doobieによるシンプルなDBアクセスコードは以下のような感じです。

import doobie.imports._

case class User(id: String, password: String, email: Option[String])

object User {
  def byId(id: String): ConnectionIO[Option[User]] =
    sql"SELECT id, password, email FROM users WHERE id = ${id}".query[User].option
}

呼び出す側はこちら

import doobie.imports._
import scalaz.effect.IO

val xa = DriverManagerTransactor[IO](
  "com.mysql.jdbc.Driver", "jdbc:mysql://example.com:3306/example", "user", "password"
)
val a: ConnectionIO[Option[User]] = User.byId("kazzna")
val b: IO[Option[User]] = a.transact(xa)
val c: Option[User] = b.unsafePerformIO

これの素晴らしいところは、接続情報がxaにまとめられていて、それを明示的に渡すということですね。

ちなみにDriverManagerTransactorの型引数はscalaz.Catchableを要求してきたりするので、 IO以外ではscalaz.concurrent.Taskぐらいしか使える型を知りません。
(きっとガチ勢がコメントしてくれるはず・・・)

テストを書いてみる

最初に書いた通り、SQLが正しいことは確認してからScalaコードを書きたいので、 ユニットテストでDBになんて繋ぎたくないんですね。

ユニットテストで確認するのは「使用したいSQLが出力されているか」までで、 外部との接続はもうちょっと結合度の高いテストでやりたいわけです。

ですが世間一般のライブラリーはいつの間にかDB接続が暗黙的に渡されていたりして、 テストしようと思うと色々なテクニック(モックとか)を使わないと難しかったりします。

ところがこのdoobieはゆるふあ仕様で、それが簡単にできるのです!!!

val a = User.byId("kazzna") // ConnectionIO[Option[User]]
val b = a.transK[IO] // Kleisli[IO, java.sql.Connection, Option[User]]

Kleisli[M[_], A, B]とはゆるふあに言えばA => M[B]です。
なのでこの場合はb: java.sql.Connection => IO[Option[User]]ですね。

つまりjava.sql.Connectionを渡せばテストできるわけです。
このinterfaceを実装しましょう。

import java.sql._

import org.scalatest._

class QueryChecker(sql: String, params: Map[String, Any], result: ResultSet) extends Connection with Matches {
  override def prepareStatement(s: String): PreparedStatement = new PreparedStatement {
    s should ===(sql)
    override def setString(i: Int, s: String): Unit = {
      params(i) should ===(s)
    }
    override def executeQuery(): ResultSet = result
    // override def ... = ??? を必要なだけ書く
  }
  // override def ... = ??? を必要なだけ書く
}

class DummyResultSet(v: List[Map[Int, Any]]) extends ResultSet {
  var rowIndex = -1
  override def next(): Boolean = {
    rowIndex = rowIndex + 1
    rowIndex < v.length
  }
  override def getString(i: Int): String = v(rowIndex)(i).asInstanceOf[String]
  // override def ... = ??? を必要なだけ書く
}

気を付けるべきことは、import java.sql._java.sql.Arrayがimportされるため、 scala.Array[A]Arrayと書けないことです。
scala.Arrayと全部ちゃんと書く必要があります。もしくは別名import。

あと、closeとかもoverrideしないとランタイムエラーが出ると思いますので、 エラーが出たところから修正していきましょう。

なお、このあたりのコードはJava用なのでmutableに書いたほうが楽だと思われます。

import org.scalatest._

class UserSpec extends FlatSpec with Matches {
  "Sql" should "be checked by test Connection" in {
    val id = "kazzna"
    val query = "SELECT id, password, email FROM users WHERE id = ?"
    val map = Map(1 -> id, 2 -> "pass", 3 -> "email@example.jp")
    val expected = Some(User(map(1), map(2), Some(map(3))))
    val a = User.byId(id).transK[IO]
    val b = new QueryChecker(query, Map(1 -> id), new DummyResultSet(Seq(map)))
    val actual = a(b).unsafePerformIO
    actual should ===(expected)
  }
}

これでDB接続情報を一度も書かずにユニットテストができました!!簡単!!

例によって例の如くQueryにはCypherを使用していますが、 一応動くサンプルをgithubにあげておきました。

github.com

最後に: 教えて怖い人!

ConnectionIOOptionT[ConnectionIO, A]EitherT[ConnectionIO, String, A]なんかにして、 2つ3つ組み合わせて途中で失敗したら実行をやめるようなqueryを書くときに型引数つらいのですが、 なんとかなる方法はありませんか?

コメントお待ちしております(>﹏﹏<);;

以上でScala Advent Calendar 2015 12日目は終了です。
お付き合いいただきありがとうございます。

明日はOE_uiaさんによる「Scala AndroidかDeepLearning的な何か」です。
レベルが高そうですが、楽しみです。