mike-neckのブログ

Java or Groovy or Swift or Golang

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

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

表題のとおりです。

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は生活拠点がアレでアレだったことを考えれば、少しは進歩してるなという感じです。

おわり

Kotlinのリフレクション(KClass)を調べた

KuickCheckを作った時にKotlinのリフレクションを一通り触ったが、完全に忘れてしまったのであらためてメモ。

ここではクラスを表すKClassとそのプロパティ、およびそれらが適用できるクラスをまとめてある。


Kotlinコンパイラーが生成するクラス

KClassのプロパティなどについて確認する前に、Kotlinコンパイラーが生成するクラスについてまとめておきます。

クイズ

次のKotlinファイルSample.ktコンパイルして生成されるクラスの数はいくつか。

@file:JvmName("Comp")

import java.io.Closeable

fun foo(bar: String): String = bar.toUpperCase()
val baz: Int = foo("baz").length

class Qux {
    val prop: String = "ooo"
    fun function(t: Int, msg: String): List<String> =
            if (t < 0) emptyList() else (1..t).map { "$msg$it" }
    fun <R: Closeable> R.tryAndClose(action: (R) -> Unit) {
        try {
            action(this)
        } finally {
            this.close()
        }
    }
    val Quux.id: String
        get() = this.name
    fun Qux.foop(): String = this.prop.plus(this.toString())

    companion object {
        @JvmStatic fun add(q: Qux, qx: Quux): Int =
                q.prop.length + qx.name.length
        fun garply(q: Quux): Garply = object: Garply {
            override val waldo: String = q.name
        }
        fun boolean(b: Bool): String = when (b) {
            Bool.OK -> "true"
            Bool.NG -> "false"
        }
    }
}

class Quux(val name: String) {
    constructor(q: Qux): this(q.prop)
}

interface Garply {
    val waldo: String
    fun lorem(q: Qux): Quux = Quux(q.prop)
    val dp: Int
        get() = 1
    companion object: Garply {
        override val waldo: String = "waldo"
    }
}

object Vip

annotation class Ano(val name: String)

enum class Ord {
    LT,EQ,GT
}
enum class Bool(val asBoolean: Boolean) {
    OK(true ){ override val int: Int = 1 },
    NG(false){ override val int: Int = 0 };
    abstract val int: Int

    fun Vip.size(): Int = 10
    val Ano.size: Int
        get() = this.name.length
    companion object {
        fun list(): List<Bool> = values().toList()
    }
}

正解

クラスは16。

解説

  1. パッケージに所属すると言われるプロパティ、関数(ここではfoo関数、プロパティbaz)は実際にはパッケージファサードと呼ばれるKtクラスに入っている。ここではアノテーション@file:JvmName("Comp")が付与されているので、SampleKtクラスではなくCompクラスが生成される。
  2. Quxクラスが生成される
  3. Quxクラスのコンパニオンオブジェクトを表すクラスQux$Companionが生成される。なお、コンパニオンオブジェクトがあるクラスのインナークラスにCompanionクラスを追加することはできない。
  4. Quxクラスのコンパニオンオブジェクトのgarply関数で返すインターフェースGarplyの匿名クラスgarply$1
  5. Quxクラスのコンパニオンオブジェクトにてwhenenumクラスを取った場合に生成されるWhenMappingsクラス。なお、enum以外でwhenによる分岐をした場合にはこのクラスは生成されない。あくまでWhenMappingsクラスはenumの場合のみに生成される。
  6. Quuxクラスが生成される。
  7. Garplyクラスが生成される。
  8. GarplyクラスのコンパニオンオブジェクトGarply$Companionが生成される。
  9. Garplyクラスのデフォルト実装を表すGarply$DefaultImplsクラスが生成される。
  10. Vipクラスが生成される。
  11. Anoクラスが生成される。
  12. Ordクラスが生成される。
  13. Boolクラスが生成される。
  14. enumのエントリーが実装を持つ場合、それぞれのエントリーごとにクラスが生成されるので、Bool$OKが生成される。
  15. 同様にBool$NGクラスが生成される。
  16. BoolのコンパニオンオブジェクトBool$Companionが生成される。

では、これらに対してKClassインスタンスを取得して、インスペクションしていく。

KClassの取得

KClassは普通に型名::classで取得すれば良いのだが、一部のクラスはこの方法が使えない。

コンパニオンオブジェクト

コンパニオンオブジェクトを生やしたクラスに続けて.Companionで取得できる。先の例でいくと、Quxクラスのコンパニオンオブジェクトを取得する場合は次のようになる。

val quxCompanionClass = Qux.Companion::class

その他

次のクラスはKotlinコードからクラスを参照できないので、JavaClass.forName(String)から取得した後、Classインスタンスに生えているkotlinプロパティでKClassインスタンスを取得する。

  • Ktクラス
  • 匿名クラス
  • WhenMappings
  • DefaultImpls
  • enumエントリー

先程の例ではGarplyクラスのデフォルト実装クラスGarply$DefaultImplsクラスは次のように取得する。

val anonymusClass = Class.forName("Garply${'$'}DefaultImpls").kotlin

なお、kotlin extensionプロパティはnullable型(KClass<T>?)ではないので、KClass型で取得できる。

name系のプロパティ

まずはname系のプロパティであるこれらのプロパティ。

  • qualifiedName - 完全修飾名
  • simpleName - クラス名のみ
  • jvmName - JVM上でのクラス名
クラス qualifiedName simpleName jvmName
Ktクラス Comp Comp Comp
通常のクラス Qux Qux Qux
コンパニオン Qux.Companion Companion Qux$Companion
匿名クラス null 1 Qux$Companion$garply$1
WhenMappings Qux$Companion$WhenMappings Qux$Companion$WhenMappings Qux$Companion$WhenMappings
インターフェース Garply Garply Garply
DefaultImpls Garply.DefaultImpls DefaultImpls Garply.DefaultImpls
オブジェクトクラス Vip Vip Vip
アノテーション Ano Ano Ano
enum(実装なし) Ord Ord Ord
enum(実装あり) Bool Bool Bool
enumエントリー Bool.OK OK Bool$OK

qualifiedNamejvmNameにはパッケージ名が付与されるが、ここではデフォルトパッケージで調べているので、パッケージ名がなくなっている。

匿名クラスのqualifiedNamenullであることがここではポイント。

コンストラクター

コンストラクターを取得できるKClassのプロパティは次の二つ。

  • primaryConstructor - プライマリコンストラクタ(KFunction<T>)を返す
  • constructors - コンストラクターすべてを返す(Collection<KFunction<T>>)

KFunctionにはnameというプロパティがあるので、それを取得してみた。

クラス primaryConstructor constructors
Ktクラス 取得できない(1) 取得できない(1)
通常のクラス <init> (<init>)
コンパニオン null ()
匿名クラス <init> (<init>)
WhenMappings 取得できない(1) 取得できない(1)
インターフェース null ()
DefaultImpls 取得できない(1) 取得できない(1)
オブジェクトクラス null ()
アノテーション null ()
enum(実装なし) <init> (<init>)
enum(実装あり) <init> (<init>)
enumエントリー null ()

(1) -- プロパティにアクセスすると例外(UnsupportedOperationException)が発生する

Ktクラス、WhenMappings、DefaultImplsはこの後で調べるプロパティも例外を返してくるほど、リフレクションでの操作がサポートされていない状況。関数なども取得できないということは、これらのクラスに入っている関数をKotlinの表現で利用することもできない。したがって、これらのクラスに入っている関数を利用したい場合はJavaのリフレクションを利用することになる。

通常のクラスにconstructorを追加すれば、もちろんconstructorsで返ってくるコンストラクターの数も増える。

インナークラス

nestedClassで取得できるクラス(KClass)。ここでは取得できたインナークラスのsimpleNameを取得した。

先の例では明示的に作成していないが…

クラス nestedClass
通常のクラス (Companion)
コンパニオン ()
匿名クラス ()
インターフェース (Companion)
オブジェクトクラス ()
アノテーション ()
enum(実装なし) ()
enum(実装あり) (Companion)
enumエントリー ()

(*) Ktクラス、WhenMappings、DefaultImplsは例外を出すので除外した

コンパニオンオブジェクトがインナークラスとして取得できる。まあ、インナークラスといえばインナークラスといえる。また、WhenMappings、DefaultImplsなどは取得できないことから、これらがインナークラスとみなされていないことがわかる。

関数

関数を取得するプロパティがいくつかあるが、まずはdeclaredFunctionsを取得してみる。declaredFunctionsCollection<KFunction<T>>を返すので、nameプロパティを取得した。

クラス declaredFunctions
通常のクラス (function,foop,tryAndClose)
コンパニオン (add,boolean,garply)
匿名クラス ()
インターフェース (lorem)
オブジェクトクラス ()
アノテーション ()
enum(実装なし) 取得できない(2)
enum(実装あり) 取得できない(2)
enumエントリー ()

(2) KotlinReflectionInternalErrorが発生する。この例外はenumクラスの関数、プロパティなどを取得しようとするとちょくちょく発生する。

declaredFunctionsはクラスの中で定義した関数(いわゆるメソッド)および、クラスの中で定義した拡張関数を返す。

また、コンパニオンオブジェクトで@JvmStaticを付与した関数も返ってくる。まあ、JavaでスタティックメソッドがClass#getMethod(String, Class...)で返ってくることを考えれば当然といえば当然なのだが…


次にdeclaredMemberFunctionsを取得する。

クラス declaredMemberFunctions
通常のクラス (function)
コンパニオン (add,boolean,garply)
匿名クラス ()
インターフェース (lorem)
オブジェクトクラス ()
アノテーション ()
enum(実装なし) ()
enum(実装あり) ()
enumエントリー ()

declaredMemberFunctionの場合はクラスの中で定義している拡張関数以外の関数が返ってくることが、Quxクラスの結果を見るとわかる。


次にdeclaredMemberExtensionFunctionsを取得してみる。

クラス declaredMemberExtensionFunctions
通常のクラス (foop,tryAndClose)
コンパニオン ()
匿名クラス ()
インターフェース ()
オブジェクトクラス ()
アノテーション ()
enum(実装なし) ()
enum(実装あり) (size)
enumエントリー ()

declaredMemberFunctionsとは異なり、クラスの中で定義している拡張関数が返ってくることが、Quxクラスの取得結果からわかる。

プロパティ

プロパティも同様に様々あるが、まずmemberPropertiesを取得してみる。この関数はColleciton<KProperty1<T,*>>を返してくる。KProperty1<T,R>KProperty<R>KCallable<R>を継承しており、nameが取得できるので、ここではnameを取得した。

クラス memberProperties
通常のクラス (prop)
コンパニオン ()
匿名クラス (waldo,dp)
インターフェース (dp,waldo)
オブジェクトクラス ()
アノテーション (name)
enum(実装なし) 取得できない
enum(実装あり) 取得できない
enumエントリー 取得できない

クラスにて取得できるプロパティが返されることが、Quxおよび匿名クラスの結果からわかる。

そして相変わらずenumで例外が発生する。


次にdeclaredMemberPropertiesを取得してみる。

クラス declaredMemberProperties
通常のクラス (prop)
コンパニオン ()
匿名クラス (waldo)
インターフェース (dp,waldo)
オブジェクトクラス ()
アノテーション (name)
enum(実装なし) ()
enum(実装あり) (asBoolean,int)
enumエントリー ()

ボディで定義したプロパティが返されることが、Quxおよび匿名クラスとGarplyインターフェースの取得結果からわかる。

declaredMemberPropertiesを使うと、enumクラスは例外が発生しない…


次にdeclaredMemberExtensionPropertiesを取得してみる。

クラス declaredMemberExtensionProperties
通常のクラス (id)
コンパニオン ()
匿名クラス ()
インターフェース ()
オブジェクトクラス ()
アノテーション ()
enum(実装なし) ()
enum(実装あり) (size)
enumエントリー ()

こちらは、ボディで定義した拡張プロパティが取得できるようである。

スタティックなんたら

staticFunctionsというのがあるので取得してみる。型はCollection<KFunction<T>>

クラス staticFunctions
通常のクラス ()
コンパニオン ()
匿名クラス ()
インターフェース ()
オブジェクトクラス ()
アノテーション ()
enum(実装なし) 取得できない
enum(実装あり) 取得できない
enumエントリー ()

@JvmStaticを付与した関数が取得できるのかと思いきや、何も取得できない。

まとめない

取り上げてないプロパティがいくつかあるが、とりあえず、ここまでわかれば大体なんとかなると思う。KotlinのリフレクションはおおよそJavaのリフレクションをKotlinの言語モデルに変換するためのラッパーという感じ。実際にKCallableの引数の型を見ても、第一引数がその関数が所属しているクラスのインスタンスだったりする。

各プロパティを取得するためのコードはgistに貼っておくので、興味ある人はやってみるといいと思う。なお、実効にはkotlin-reflection.jarが必要になるので、そこだけ注意が必要。

KotlinのKClassプロパティを調べた · GitHub

KotlinでJSR303(Bean Validation)を使うときの注意

少しハマったのでメモ。

KotlinでBean Validationを使った時に、validationされないので、Stackoverflowなどを読んでいたら、アノテーションの書き方を工夫する旨あった。

まずはvalidationされないパターン

data class Person (
        @NotEmpty
        val name: String,
        @Min(18)
        val age: Int)

この場合、アノテーションコンストラクターに付与されたとみなされてしまうのでvalidationされないとのこと。

validationされるパターン

data class Person (
        @get:NotEmpty
        val name: String,
        @get:Min(18)
        val age: Int)

get:で始めると、getterの方にアノテーションが付与されるので、Bean validationに通すことができる。