来世から頑張る!!

技術ブログを目指して

IndexedContを使ってみよう: 継続編

この記事は?

IndexedCont便利なのでみんな使ってほしいとの思いの元、前提となる知識から解説する記事です。

前回はfor式を使ったエラーハンドリングの基本的なことを書きました。

なお、今回もIndexedCont出てきません

継続

今回も引き続きforを使ってエラーハンドリングしていきます。

相変わらずソースコードはメモにスマートフォンで直書きなので、コンパイルしていません。
きっとそのうち修正されます。

Cont データ型

今回はContという型を使ってエラーを処理していきます。

case class Cont[R, A](run: (A => R) => R)

object Cont {
  def point[R, A](a: A): Cont[R, A] = Cont(f => f(a))
}

あまり型パラメーターや関数型プログラミングになじみのない方には謎な表現だと思われますが、ぜひ使ってほしいので丁寧に説明します。

覚えておいてほしいのは、ほとんどの場合構造を頭で理解するよりも使って慣れていく方が簡単だということです。

Contとは

たぶんContinuation(継続)の略です。

唯一の変数であるrunの型は「[(A => R)という関数を受け取ってRを返す]関数」です。

  • R: 目指す結果(Result)
  • A: 現在の値
  • A => R: 現在の値から結果までの続きの処理(継続)
scala> val a = Cont.point[String, Int](42)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.run(i => i.toString)
b: String = 42

map

Contmapを定義します。

case class Cont[R, A](run: (A => R) => R) {
  def map[B](f: A => B): Cont[R, B] = Cont(g => run(f andThen g))
}

AからR向かう処理の途中でf: A => BというAからBまでの処理を受け取ったので、残りはB => Rまでとなるのがmapです。

scala> val a = Cont.point[String, Int](65)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.map(_.toChar) // 現在の値はCharの 'A' (たぶん)
b: Cont[String, Char] = Cont(<function>)

scala> val c = b.map(c => List(c, c, c)) // 現在の値はList('A', 'A', 'A')
c: Cont[String, List[Char]] = Cont(<function>)

scala> val d = c.run(_.mkString("-"))
d: String = A-A-A

普通のプログラミングの1行ずつがすごく難しそうになっただけですね。

エラー処理(継続の破棄)

Contの何が便利かというと、runの引数で渡ってきたA => Rは必ずしも使わなくても良いということです。

使い道は無いもののわかりやすい例として、数値の処理はするけれども100以上は扱わない関数を作ってみます。

def smallOnly(i: Int): Cont[String, Int] = Cont { f =>
  if (i < 100) {
    f(i)
  } else {
    "大きすぎてムリです。"
  }
}

iが100以下ならば続きの処理をiに対して実行するけれど、それ以外の場合は既にあきらめるContを作れます。

scala> val a = smallOnly(65)
a: Cont[String, Int] = Cont(<function>)

scala> val b = a.map(_.toChar) // 現在の値はCharの 'A' (たぶん)
b: Cont[String, Char] = Cont(<function>)

scala> val c = b.map(c => List(c, c, c)) // 現在の値はList('A', 'A', 'A')
c: Cont[String, List[Char]] = Cont(<function>)

scala> val d = c.run(_.mkString("-"))
d: String = A-A-A

scala> val o = smallOnly(101) // iが100以上なので Cont(_ => "大きすぎてムリです。")
o: Cont[String, Int] = Cont(<function>)

scala> val p = a.map(_.toChar) // Cont(f => a.run(i => f(i.toChar))) だけど、a.runには何を渡しても無視される。
p: Cont[String, Char] = Cont(<function>)

scala> val q = b.map(c => List(c, c, c)) // 同上
q: Cont[String, List[Char]] = Cont(<function>)

scala> val r = c.run(_.mkString("-"))
r: String = 大きすぎてムリです。

なんとなくエラー処理に使えそうではないでしょうか。

flatMap

for式と言えばflatMap(詳しくはWebで)なので、定義していきましょう。

case class Cont[R, A](run: (A => R) => R) {
  // map省略
  def flatMap[B](f: A => Cont[R, B]): Cont[R, B] = Cont(g =>run(a => f(a).run(g)))
}

これでforの中でContが使えるようになります。

for {
  a <- Cont.point[String, Char]('a')
  b <- Cont.point[String, Int](3)
} yield (a.toInt + b).toChar
// => Cont[String, Char](f => f('d'))

Contによるエラー処理

前回の記事で定義したgetAなどを使って、 Eitherのように失敗した位置のわかる合成をしてみましょう。

注意点としては、Either[E, A]がエラー時の型Eと成功時の型Aを返してきていたのに対し、 Cont[R, A]では最終的にRの型一つしか返せないということです。

そのため、結果の型を以下のように定義します。

sealed trait Result

// 失敗系
case class ANotFound(id: AID) extends Result
case class BNotFound(id: BID) extends Result
case class CNotFound(id: CID) extends Result

// 成功
case class Succeed(a: A, b: B, c: C) extends Result

成功した場合も失敗した場合もResultということになります。
先ほどStringで"大きすぎて・・・"と失敗を表現していたのと同じですね。

def resolveA(id: AID): Cont[Result, A] = Cont { f =>
  val a: Option[A] = getA(id)
  val b: Option[Result] = a.map(f) // getAがSomeならば継続`f`を適用
  val c: Result =b.getOrElse(ANotFound(id)) // Noneならば`f`は無視してANotFound
  c // 一行書くならで getA(id).fold(ANotFound(id))(f)
}

def resolveB(id: BID): Cont[Result, B] = Cont(f =>getB(id).fold(BNotFound(id))(f))
def resolveC(id: CID): Cont[Result, C] = Cont(f =>getC(id).fold(CNotFound(id))(f))

def resolve(aID: AID): Cont[Result, Succeed] = for {
  a <- resolveA(aID)
  b <- resolveB(a.bID)
  c <- resolveC(b.cID)
} yield Succeed(a, b, c)

実行は以下のような感じです。

scala> val a = resolve(AID(5)).run(x => x)
a: Reslut = ANotFound(AID(5))

scala> val b = resolve(AID(6)).run(x => x)
b: Reslut = BNotFound(BID(12))

scala> val c = resolve(AID(19)).run(x => x)
c: Reslut = CNotFound(CID(11))

scala> val c = resolve(AID(5)).run(x => x)
c: Reslut = Succeed((A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8)))

EitherContの使い分け

ここまで見ると中身は大きく違うものの、EitherContでは大体同じような用途に使えそうなことがわかります。
どちらもforの上から順に処理を実行していき、どこかで失敗すると続きは実行されずにエラーケースになります。
では、どのように使い分けるのでしょうか。

一番重要なポイントは戻ってくる値の型です。 Either[E, A]では処理がすべて終了した後でもEAの二つの型が返ってきます。

それに対してCont[R, A]では、値を取り出すためにrunする必要があり、 runした結果返ってくるのはR型ただ一つだけです。

これはつまりEitherが失敗した場合に失敗したことを関数の呼び出し側に伝え、エラー処理の分岐を任せているのに対し、 Contではエラー処理自体も関数の中で完結していることを表します。

Contを返すということは「エラー処理はやり終えた」ということを伝える意思表示になっています。

もちろんmatchや正規表現などを使ってRの中身を調べての分岐は不可能ではありませんが、やるべきではないでしょう。

よって、使い分けの目安としては以下のようになります。 - Either: 中間処理など、エラーは伝えたいけれどどう処理するかは決めたくない部分 - Cont: 外部入出力など、エラーの場合でも何か正常な処理を返さないといけない部分

次回への継続

次回はいよいよ本題であるIndexedContを扱いたいと思います。

IndexedContを使ってみよう: 前提知識編

この記事は?

IndexedCont便利なのでみんな使ってほしいとの思いの元、前提となる知識から解説する記事です。 なお、次の次の回ぐらいまでIndexedCont出てきません

本編

この記事ではエラーハンドリングのためにIndexedContを使えるようになることを目標とします。

Monad Transformerには触れませんので、別記事を探してください。

Step 1: Optionによるエラーハンドリング

理解しやすいエラーハンドリングの基本として、Optionを使用したエラーハンドリングの例からスタートします。

対象コード

必要十分な最低限の例として、下記のようなオブジェクトを取り扱います。

case class AID(value: Int)
case class BID(value: Int)
case class CID(value: Int)

case class A(id: AID, bID: BID)
case class B(id: BID, cID: CID)
case class C(id: CID)

case clas Found(a: A, b: B, c: C)

// 取得用のインターフェース(traitなどに定義): 見つからない場合はNoneを返す
def getA(id: AID): Option[A]
def getB(id: BID): Option[B]
def getC(id: CID): Option[C]

def find(aID: AID): Option[Found]

findを実装するのが目的となります。

実装

取得用インターフェースはわかりやすい例として下記のように実装してみます。

def getA(id: AID): Option[A] =
  if (id.value % 2 == 0) Some(A(id, BID(id.value * 2)))
  else None

def getB(id: BID): Option[B] =
  if (id.value > 12) Some(B(id, CID(id.value - 8)))
  else None

def getC(id: CID): Option[C] =
  if (id.value < 10) Some(C(id))
  else None

これに対して、findの実装は以下のようになります。

def find(aID: AID): Option[Found] = for {
  a <- getA(aID)
  b <- getB(a.bID)
  c <- getC(b.cID)
} yield Found(a, b, c)

解説

Optionのfor式はどこかでNoneが返ってきたらその行以降は実行されずにNoneが返ります。

scala> find(AID(5)) // Aが見つからない
res0: Opiton[Found] = None

scala> find(AID(6)) // Bが見つからない
res1: Opiton[Found] = None

scala> find(AID(18)) // Cが見つからない
res2: Opiton[Found] = None

scala> find(AID(8)) // すべて見つかる
res3: Opiton[Found] = Some(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))

Step 2: Eitherを使用してエラー内容を特定する

Optionではどこか途中で失敗すると、どこで失敗してもNoneとなります。

どこで失敗したかを知りたい場合の一番シンプルな方法がEitherです。

対象コード

Optionの例そのままで、findだけ変更します。

def find(aID: AID): Either[String, Found]

実装

こちらもfind以外はそのままです。

def find(aID: AID): Either[String, Found] = for {
  a <- getA(aID).toRight(s"AID: ${aID}のAが見つかりませんでした。")
  b <- getB(a.bID).toRight(s"BID: ${a.bID}のBが見つかりませんでした。")
  c <- getC(b.cID).toRight(s"CID: ${b.cID}のCが見つかりませんでした。")
} yield Found(a, b, c)

解説

EitherOptionと同様に1度でもLeftになるとそれ以降の行は実行されません。

scala> find(AID(5)) // Aが見つからない
res0: Either[String, Found] = Left("AID: 5のAが見つかりませんでした。")

scala> find(AID(6)) // Bが見つからない
res1: Either[String, Found] = Left("BID: 12のBが見つかりませんでした。")

scalva> find(AID(19)) // Cが見つからない
res2: Either[String, Found] = Left("CID: 11のCが見つかりませんでした。")

scala> find(AID(8)) // すべて見つかる
res3: Either[String, Found] = Right(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))

ただし、ひとつのfor式の中で使われるEitherはすべて左側の型が同じである必要があります。

応用

左側がStringでは扱いにくいので、自身で定義した型をここに入れたりできます。

sealed trait FindingError

case class ANotFound(id: AID) extends FindingError
case class BNotFound(id: BID) extends FindingError
case class CNotFound(id: CID) extends FindingError

def find(aID: AID): Either[FindingError, Found] = for {
  a <- getA(aID).toRight[FindingError](ANotFound(aID))
  b <- getB(a.bID).toRight(BNotFound(a.bID))
  c <- getC(b.cID).toRight(CNotFound(b.cID))
} yield Found(a, b, c)

次回にむけて

次回はIndexedContのシンプルな形Contを扱います。

たぶん今回の記事にも時間があれば追記します。 (コードを実際に動かす時間がなかったので動かしてみて修正したりします。)

ボードゲームのハコについて

このエントリーはボドゲ紹介 Advent Calendar 2017の7日目です。

前日: 推理ゲーム『ワトソン&ホームズ』 - kano-e no memo
翌日: テラフォーミング・マーズの魅力: うぃりあむの「ヴァリアントの森」

タイトルの通り、ボドゲのハコを紹介したいと思います。

先日ゲームマーケット2017が実施されましたが、そのなかでとある出展者さんが「ダイスタワーという言葉を知らない人が検索フォームからダイスタワーに出会う可能性は限りなく低い」というようなお話をしており、私も知らなければ出会う可能性がすごく低いものを紹介しようとこのエントリーにしました。

ここではBox insertと呼ばれるアクセサリーを紹介致します。

Box insertとは

ボードゲームの箱って内容物に対して小さすぎて、中身を小分けするとフタが閉まらなかったりしますよね?
拡張も一緒に持ち運びたいのに、スペースが足りずに諦めたりしますよね?

f:id:kazzna:20171207221826j:plain
ボードゲームコンポーネントと入りきらない拡張

空気が主なコンポーネント? そういうボードゲームの事は一旦忘れましょう。

こういった大量のコンポーネントをうまく詰め込めるように作られた箱がbox insertです。

Box insertを使うと、先程のようなコンポーネントがうまく箱に収まります。 f:id:kazzna:20171207222041j:plain f:id:kazzna:20171209001801j:plain

f:id:kazzna:20171207222116j:plain
拡張とともに1箱へ
通常はスペースなく箱の中身がきっちり埋まるように作られているので、蓋が開かないように留めてしまえば縦置きも可能です。

注文と組み立て

自分で組み立てする形態で売られていることがほとんどで、注文すると数週間程度で下の写真のような板が届きます。 f:id:kazzna:20171207222315j:plain

組み立てには以下のものが必要になるので100均で揃えてください。

  • 木工用ボンド
  • 紙ヤスリ(120番と400番あたりがあると大丈夫です。)
  • 軍手
  • 綿棒(大量にあるといい感じ)
  • ゴムハンマー(なくても大丈夫かも)

基本的にはプラモデルと同じで、枠から取り外して説明書の通りにくっつけるだけです。
ただ、木製なので細いパーツが折れやすかったり、トゲが手に刺さったりするのでそこは注意が必要です。 万が一パーツが折れた場合は、要らないフレーム部分をヤスリで削って出てきた粉とボンドを混ぜてくっつけることが可能です。

1時間ぐらいで完成すると書かれていますが、そこはボードゲームと同じで大体半日はかかります。
慣れた人が回せば早いんでしょうね。

ニスとかはよく分からないので詳しい人教えてください。

販売サイト紹介

box insertは大きな会社から個人の副業っぽいところまで、多数のサイトで販売されています。
色々と探し回ってあっちがいいこっちがいいと比べて回るのが楽しさの醍醐味です!!!

個人的に気になるところをいくつかピックアップしてご紹介致します。

Meeple Realty

Think inside the box(箱の中について考える)がテーマのサイトです。
ただの木箱ではなくて、ゲームのテーマに合った形に加工されているのが特徴です。 その分使い勝手は他に少し劣ることもありますが、インサートなしよりは確実に使いやすいのでデザインを重視する人にオススメです。

Daedalus Productions

冒頭のテラミスティカのインサートを販売しているところです。
Quick-Start Insertという取り出してすぐにプレイできるようにと作られたシリーズの使い勝手が素晴らしい出来です。

組み立ての説明書がサイト上で公開されていますので、イメージをつかみたい場合にどうぞ。

The Broken Token

インサートだけでなく、オーガナイザー(organizer)やコインなども販売しているサイトです。
プラスチック性の蓋などの木製だけじゃないコンポーネントがついていたり、カードのケースがそのままカードフィーダーになったりと色々素敵なものが沢山あります。

最近追加されたテラミスティカのインサートの出来も良さそうなので、もし既に持っていなければここのを買っていたと思います。

Etsy

ハンドメイド作品を気軽に販売できるサイトです。
アクセサリーや服から大型家具まで幅広く売られており、ゲーム関連アクセサリーも大量にあります。

販売者は個人の場合も多数あるようで、普通のお店の感覚では挑まない方が無難かもしれません。
コンポーネントの質はきっとピンきりなんでしょうけど、お値段の割に非常に良いものも多いので探す価値は大です。

システム的にはカートに入れるボタンを押して支払いを済ませれば買えるのですが、他の海外ショップでお問い合わせぐらいはできるようになってからの利用をオススメします。

Insert here

フォームコアによるインサートを作っているサイトです。 家族でやっている?らしいです。
フォームコアはプラスチックっぽい謎の物体で、ネットで調べる限りでは木の箱よりも軽そうです。

木製のインサートはそれなりの重量になるので、軽いならいつかチャレンジしてみたいと思っています。

その他の気になるサイト

インサートじゃなかったりもしますが、気になるサイトをいくつか。

選び方のポイント

正直、箱に何を求めるかなんて人それぞれだと思いますが、私の経験と好みからの選ぶ基準を記載しておきます。

カードがスリーブ付きで入るのかどうか

トークンの多いゲームでは特に、スペースの都合でカードはスリーブ無しでしか格納できないものもあります。
別のサイトで売られているものはスリーブ付きでも入るということもままあるので、スリーブの有無は確認しましょう。

蓋は閉まらなくてもそこまで困らない

インサートによっては箱の上端より数センチ上まで飛び出ているものもありますが、蓋が一番下までいかなくても意外に困らないものです。
見た目にこだわる場合は避けるべきでしょうし一概には言えませんが、うまくバランスの取れているものを選びたいものです。

みんなに配る駒は別々のトレイに

それぞれのプレイヤー用トークンや開始時の手札など、配ってしまうものはそれぞれ別のトレイに入っていてそのまま配れるものの方が便利です。
また、コインなどの共通のストックに置いておくトレイが2つに分かれていると、それぞれをボードの左右に置いたりなど便利さが上がるのでチェックしておくとよいと思います。

何が格納できるのか

拡張なども一緒に格納できるのかどうかの確認は重要です。
拡張を持っていない場合拡張入れは空っぽになるものの、そのトレイを入れないと中で崩れるので空のまま入れておかなければ傾けられません。
中には「プロモボードがないと隙間ができるから縦向けにはできないよ」というものもあるので注意してください。

お気に入りの箱を探す際の参考程度にどうぞ。

以上、良いボードゲームライフをお過ごしください。

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 〜を使おう。
こっちは自分でインデックス貼れるのでもっと早いよ!!!