読者です 読者をやめる 読者になる 読者になる

mike-neckのブログ

JavaかJavaFXかJavaEE(なんかJava8が多め)

Kotlinの末尾再帰でFizzBuzz(Kotlin Advent Calendar 2016) #ktac2016

Kotlin

この記事はKotlin Advent Calendar2016の6日目の記事です。

昨日は RyotaMurohoshiさんの 「【!ってなんだ】KotlinとJava、nullとPlatformType【NullableにNotNull」 でした。

明日は kikuchy さんの 「」 です。


末尾再帰FizzBuzz

この記事ではKotlinの末尾再帰を用いて、ややオーバーエンジニアリングなFizzBuzzのプログラムを書いていきます。 なお、この記事で用いたKotlinのバージョンは1.1-M02-8です。後のアップデートによっては動作しないことがありますが、ご了承ください。

FizzBuzzの型

まず、FizzBuzzの型を定義します。FizzBuzzの抽象的な型を Value とします。次の二点を考慮して Value を定義します。

  • Value は順列なので、次の値を持ちます。
  • Value は最終的に表示するので、文字列への変換を持ちます。

次に具体的な型を考えます。FizzBuzzの値として数値、Fizz/Buzz/FizzBuzz、 また順列の末端要素を用意します。

sealed class Value {
  abstract val show: String
  abstract val next: Value
}

object Term: Value() {
  override val show: String get() = throw UnsupportedOperationException("This is term")
  override val next: Value get() = throw UnsupportedOperationException("This is term")
}
class Num(val value: Int, override val next: Value): Value() {
  override val show: String = "$value "
}
class Fizz(override val next: Value): Value() {
  override val show: String = "Fizz "
}
class Buzz(override val next: Value): Value() {
  override val show: String = "Buzz "
}
class FizzBuzz(override val next: Value): Value() {
  override val show: String = "FizzBuzz\n"
}

FizzBuzz オブジェクト生成

定義した Value を生成していきますが、少しだけ考える必要があります。

単純に1から順番に Valueマッピングしていくと 1 -> 2 -> 3 の順番で Value(n) が作られます。 Value は次へのリンクを持ちますが、これは生成時にすでに存在している Value へのリンクになるため辿っていく順番は Value(3) -> Value(2) -> Value(1) の順番になります。 しかしFizzBuzzでは1から表示していきたいので、これでは不都合です。 Value(n) を作ったときにそのリンク先を一つ後の Value(n + 1) が設定されるようにするために、生成されたばかりの Value を引数にとって先頭の Value(つまり Value(1))を返す関数を作っていきます。また、その関数に Gen という別名を与えます。

typealias Gen = (Value) -> Value

val num: (Int) -> (Gen) -> Gen = { n -> { f -> { v -> f(Num(n, v)) } } }
val fizz:         (Gen) -> Gen =        { f -> { v -> f(Fizz(v)) } }
val buzz:         (Gen) -> Gen =        { f -> { v -> f(Buzz(v)) } }
val fizzBuzz:     (Gen) -> Gen =        { f -> { v -> f(FizzBuzz(v)) } }

なぜこんな面倒くさい関数を作っているかという点に関しては後ほど説明します。

カウント

3の倍数と5の倍数を調べるためのループカウンターを作ります。この型のプロパティには、同じ型で次のカウントを表す next を設定します。 次にこの型に 1 -> 2 -> 3 -> 1 -> ... とループするように最大値をもたせます。 この実装はループの途中を Mid 、ループの終端を End とします。

また、ループカウンターをそのまま末尾再帰関数の呼び出し回数を数えるのにも利用します。末尾再帰の呼び出し回数を数えるものについては Count という 別名を与えます。

interface Succ<L: Succ<L>> {
  val next: L
}

sealed class Loop: Succ<Loop> {
  abstract val max: Int
}

class Mid(val current: Int, override val max: Int): Loop() {
    override val next: Loop get() = if (current == max - 1) End(max) else Mid(current + 1, max)
}
class End(override val max: Int): Loop() {
    override val next: Loop get() = Mid(1, max)
}

typealias Count = Loop

関数用の補助

関数の補助をする関数を作ります。

関数 (P) -> Q と関数 (Q) -> R を合成する拡張関数 plus(P) -> Q につけます。 また、引数をそのまま返す関数 id を作ります。

inline infix operator fun <P, Q, R> ((P) -> Q).plus(crossinline f: (Q) -> R): (P) -> R = { f(this(it)) }

fun <P> id(): (P) -> P = { it }

末尾再帰関数

ではFizzBuzzの列を構築していきます。

tailrec fun run(count: Count, three: Loop = Loop(3), five: Loop = Loop(5), result: Gen = id()): Value =
    when(count) {
      is End -> result(Term)
      is Mid -> when(five) {
        is End -> when(three) {
          is End -> run(count.next, three.next, five.next, fizzBuzz(result))
          is Mid -> run(count.next, three.next, five.next, buzz(result))
        }
        is Mid -> when(three) {
          is End -> run(count.next, three.next, five.next, fizz(result))
          is Mid -> run(count.next, three.next, five.next, num(count.current)(result))
        }
      }
    }

また Value を表示するための末尾再帰関数も作ります。

tailrec fun show(value: Value): Unit = when(value) {
  is Term -> Unit
  else    -> show(value.next.apply { print(value.show) })
}

生成した値をそのまま返すのではなく、関数にくるんで再び関数を適用していくやりかたを継続渡し(CPS)というらしいのですが、これを用いたところにKotlinで末尾再帰を書くときのコツがあります。関数ではなく、オブジェクトの順番に気をつけて Value をそのまま返すやり方を使ったらおそらくこのようになるでしょう。

tailrec fun run(count: Count, three: Loop = Loop(3), five: Loop = Loop(5)): Value =
// 中略
// Num だった場合
  is Mid -> Num(count.current, run(count.next, three.next, five.next))

しかし、このように定義した場合、関数 run は残念ながら末尾再帰になりません。

関数を末尾再帰にする場合、その関数は関数内部で一番最後に呼び出される関数にする必要があります。上記の場合であれば run 関数が呼ばれた後に Num コンストラクターが呼ばれており、再帰呼出しではあっても、末尾呼び出しではありません。このような場合、Kotlinコンパイラーは run を呼び出している箇所に「Recursive call is not a tail call」、 run 関数に「A function is marked tail-recursive but no tails call are found」と警告を出します。

そこでオブジェクトのリンクを作る処理を関数に閉じ込めて、再帰処理が終わった後に改めてその処理を実行するようにするため関数を作っていくようにしたわけです。

呼び出し

最終的に最大値(Int)からFizzBuzzの列(Value)を構築して、それを表示する関数(show)を呼び出して終わる(Unit)関数を作ります。

fun from1(max: Int): Count = Mid(1, max + 1)
val runApp: (Int) -> Unit = ::from1 + ::run + ::show

fun main(args: Array<String>): Unit = runApp(30)

Kotlinの末尾再帰によるFizzBuzzでした。末尾再帰は結果となる値を

  • 継続渡しスタイル(今回の関数)
  • 循環するデータにする(今回の Value のような構造)

ことがポイントです。

おわり

Docker復習

docker

歳なので覚えるのが遅くなったというよりも忘れるのが早くなった感じらしく、Dockerのことをすっかり忘れてたので、復習した。Dockerの使い方レベルなので自分が得するだけのエントリー。

dockerコンテナに環境変数を渡す

-e VARIABLE=VALUE で渡す。

docker run -it --rm mysql \
  -e MYSQL_DATABASE=sample \
  -e MYSQL_USER=user \
  -e MYSQL_PASSWORD=password \
  -e MYSQL_RANDOM_ROOT_PASSWORD=yes

dockerのポート番号の指定

-p ローカルのポート:コンテナ内部から見たポート で渡す

dockerのディレクトリのマウント

ローカルにあるディレクトリをコンテナにマウントすることができる

-v ローカルのディレクトリ/コンテナのディレクトリ

mysqlイメージ起動時に指定する変数

ポート番号

  • mysqlのイメージはポート3306をexposeしている

環境変数

名前 意味
MYSQL_DATABASE データベース名(USER/PASSWORDを指定する必要がある)
MYSQL_USER データベースのユーザー名
MYSQL_PASSWORD 上記ユーザーのパスワード
MYSQL_RANDOM_ROOT_PASSWORD ルートパスワードをランダムにするか(yes:する,no:しない)

データディレクトリ等

最近起動したDockerコンテナをkillする

docker killdocker ps -q を組み合わせる

docker kill `docker ps -q`

Freeモナドを使ってIOアクションをモッキングする的な

haskell

Freeモナドがすごい的な話がおよそ4〜5年前くらいに流行ってたのだが、その頃はHaskellなど触ってなかった僕には関係のない話でした。

Haskellを触ってて、IOアクションのあるようなプログラムをテスト的に動かしてみようとすると途端に困るわけです。

例えば 0 が入力されるまでに入力された正の数値の合計を出力するプログラムを考えます。

getSum :: Int -> IO Int
getSum s = do
  i <- readLn
  case I `compare` 0 of
    LT -> getSum s
    EQ -> return s
    GT -> getSum $ I + s

main :: IO ()
main = do
  s <- getSum 0
  print s

さて、このプログラムをテストすることを考えます。考えますが、こんなのコンパイルして手打ちで値を入力すればokですね。でも、そうではないんですよ。テストは極力自動でできるようにしておきたいのですよ。HUnitとかQuick Checkとかぶっこんだところで、どうやって readLn 関数から値を入れればいいのでしょう?

ということで、ここで登場するのがFreeモナドらしいわけです。

Freeモナドでモックするあたりについては次のエントリーStackoverflowが詳しいです。

d.hatena.ne.jp

stackoverflow.com

{-# LANGUAGE DeriveFunctor #-}
module MockIO where

import Control.Monad.Trans.Free
import Data.Foldable(traverse_)
import Data.IORef

-- 各IO関数を表す型の定義
data StrIO n = GetLine (String -> n) | PutStrLn String n deriving (Functor)

-- FreeモナドでくるんだIO関数型
-- ActionはFreeT StrIO IOなのでMonad
type Action n = FreeT StrIO IO n

-- プログラムから呼び出すIO関数(IO関数型を返す)
-- getLineに相当する関数
getString :: Action String
getString = liftF $ GetLine id
-- putStringに相当する関数
putString :: String -> Action ()
putString s = liftF $ PutStrLn s ()

ここまでの StrIO の定義によって、各プログラムが呼び出すIO関数を標準入出力から内部的な入出力を模した関数へと変更できるようになります。

そして、先程の 0 が入力されるまでの合計を出力するプログラムは次のように書き直せます。

-- readLnを表す型を作ってないのでStringを受け取ってIntとして読み込む関数を作っておく
-- getLine(:: IO String)の代わりにgetString(:: Action String)関数を用いる
getInt :: Action Int
getInt = fmap read getString

getSum :: Int -> Action Int
getSum s = do
  I <- getInt
  case I `compare` 0 of
    LT -> getSum s
    EQ -> return s
    GT -> getSum $ I + s

-- main関数の代わりにアプリを表す関数
app :: Action ()
app = do
  s <- getSum
  putString $ show s

そして、 Action aIO a に模す関数を作ります。

-- Action a を IO a に変換する関数が 動作モードなので、これを RunMode a と名付ける
type RunMode a = Action a -> IO a

-- 標準入力に接続する関数
stdIO :: RunMode a
stdIO io = do
  iterT go io
  where
    go (PutStrLn s n) = putStrLn s >> n
    go (GetLine f)    = getLine >>= f

-- Stringのリストを入力として扱う関数
debugIO :: [String] -> RunMode a
debugIO txt io = do
  que <- newQue txt
  iterT (go que) io
  where
    newQue = newIORef . cycle
    go _   (PutStrLn s n) = putStrLn s >> n
    go que (GetLine f)    = do
      txt <- popQue que
      f txt
      where
        popQue ref = atomicModifyIORef ref $ \(x:xs) -> (xs, x)

-- RunMode a を受け取って、実際にアプリケーション(:: Action a) を IO a に変換する関数
runAppOn :: RunMode a -> Action a -> IO a
runAppOn f = f

最後に定義した runAppOn 関数を main にて呼び出します。

main = IO ()
main = let dummy = fmap show $ [1..5] ++ [0] in
  runAppOn (debugIO dummy) app

これで、手で数値を入力しなくてもアプリケーションを試せるようになりました。

(眠いので)おわり

JJUG CCC 2016 Fall に行ってきた #jjug_ccc

java 勉強会

表題のとおりです。

www.java-users.jp

以下、参加したセッションの独断と偏見によるレポートです。


Prepare for Java 9 : Java9に備えよう

Java9先取りのセッションです。

  • Jigsaw
  • Kullar
  • 非互換情報
    • ファイル/ディレクトリ構造が変わる話
    • プロパティファイルの読み込みがデフォルトでUTF8に(今更)なるよ(ライブラリーでいい感じにしてるやつ死ぬよ)
    • JVMのログのフォーマット変わるので、このままだとパーサーは死ぬ
    • _ という名前は禁止になるのでHadoopは死ぬ
    • ほか

あ、うん、まあ大体知ってた

資料公開されてた

www.slideshare.net

SIreもはじめる、わたしたちのDevOps

しょぼちむが黒初のロングになってて、美人になったなーと思ってた束の間、「I have Dev, I have Ops. Uhh DevOps」と歌いだして、

でした。

内容的にはDevOpsを実現していくにあたってのエンジニアのスキルセット/マインドセットなどが訴えられていました。

  • 変化に強くなる
  • 自分の強みを見出す

エンジニアとしてこのあたりは常に見つめていきたいところとのこと。

資料公開されてた

www.slideshare.net

docs.com


先取りElasticsearch5.0ハンズオン!

Elasticsearchをさわろうさわろうと思って重い腰が上がらないマンなので参加。

以前に大谷さんに教えてもらったときとほぼ同じ内容でした。多分、これは自発的に触らないと覚えられない。


JVMのトラブル解決のためにやったこと -- メモリー/スレッド

GC/スレッドモデルの復習とツールおよび実例の解説です。

スピーカーはJavaだけでなくpythonのメモリ管理についても詳しく、これまでメモリで苦労してきたことが伺えました。

しかし、それらの知識/経験がすべて口頭で語られるだけでお世辞にも理解しやすいとは言い難いものでした。また資料は見出し/何の加工もしてないスクリーンショットだけで、GCやスレッドの初学者が学び始めるには情報が少なく、GCやスレッドのことを理解している人には満足できないのではないかと思いました。さらにセッション自体の目標が曖昧だったのももったいないという印象です。

JPAとDDDの関係で僕が思っていること

JPAとDDDについて、openGL-8080さんが考えてきたことが丁寧にまとめられていました。

  • DDDとJPAの関係
    • DDDのレイヤーをシンプルなDBライブラリーで実現しようとすると途端に難しくなる
    • JPAのマネージドオブジェクトのライフサイクルが難しいのは、DBのテーブル/SQLから理解しようとしているからで、DDDの観点から考えるとDBの内容があたかもメモリ上のコレクションと同等に扱えるように見える。
  • JPAの実装の違い
    • RI(Eclipse Link)での動作とJPAの仕様がすでに異なっており、RIで動くからといってそれが仕様だと誤解するとハマる

資料公開されてた

qiita.com

Featherweight JavaやGroovyの漸進的型付けについて

これは悔しいけどあまり理解できなかったです。

Javaの記述を形式的に扱うことによって、型を解決する的な問題だと思うのですが、この認識すら誤っているかもしれません。FJ(Featherweight Java)の考えを元に小田好き先生が考えてScalaコンパイラーが生まれてきたとかなんだとかということらしいですが、これも僕の聞き間違いかも…

まあ、FJ?の?型のことについて聞いたときに、Groovyの def のことかと思ってたら、実際に def のことだったのが唯一の救いでしょうか。まあ、ちゃんと知りたければ論文嫁ということですね。


その他、何人かの方と「はじめまして」しました。

昨年のJJUG CCC Fallは失意の中でのアレでアレだったし、今年のJJUG CCCのSpringは生活拠点がアレでアレだったことを考えれば、少しは進歩してるなという感じです。

おわり