Keep on moving

あんまりまとまってないことを書きますよ

2020振り返り

Python界隈の魅惑の何かです。久しぶりに今年の振り返りをしたいと思います。

発表

Covid-19の今年はあんまり外で話せなかったなという印象。自分から話に行くのではなく、受け身になっちゃったなとか割と反省点は多い。 そんな中でもいくつか話せて楽しかった。

masahito.hatenablog.com 初めてのオンラインでの発表を経験した。オンラインは話すの難しいなというのも感じましたね~

pyconjp.blogspot.com pyconjp.blogspot.com 実は2018,2019と連続で話させていただいていました。CfP通ったのでオンラインでも話せるように準備だけしてました。 また再開したらCfPを出させてもらおう。(pycon kyushuは今のところCfPが皆勤賞だったりします 😆

OSS活動

今年はあまり時間が取れなかったので、気がついたtypoにPR投げたりとかしていた。 仕事で使ってるやつに少しはPR投げられたのでまた来年にでも。

[BEAM-11523] Bump Gradle to 6.7.1 by masahitojp · Pull Request #13414 · apache/beam · GitHub

仕事

毎日会社に通ってた毎日からフルリモートの生活に変わって最初は非常に戸惑ったんだけど、段々慣れてはきたかなと。 チームビルディング的なことをやったり、(実は自分ではあんまり興味がなかった)振り返りとかを久々にチームでやるようになったり とか変化が大きかったなと思う。

買ってよかったもの

マイク & オーディオインターフェース

ベリンガー 2入力2出力 USBオーディオインターフェース UMC22 U-PHORIA

ベリンガー 2入力2出力 USBオーディオインターフェース UMC22 U-PHORIA

  • 発売日: 2018/09/01
  • メディア: エレクトロニクス

zoomでの会議が増えたこともあり購入。これに安めのアームをつけて受けて運用してる。 フルリモートになってから話す機会が増えたこともあり、話してる内容がクリアに相手に伝わるので重宝している。 ちなみに初めて話す方からから [マイクすごいっすね] と言ってもらえるのである程度アイスブレイク的な会話のネタにできるのも 助かっています。

イヤフォン

出張や運動用にこっちを買って使っていた。

が、zoomでの会議が増えてTwitterでも話題になっていたこともあり思い切って骨伝導のやつを買ってみたんですが大当たりでした。

特に一時間以上のミーティングでも耳が痛くならないので重宝してます。

USB type-c hub

出張用に買ったこれを在宅でも使っている。値段の割にGigabit Etherに対応してくれていて重宝している。

M1 Macbook Air

ARMで動くとか面白いじゃんと思って購入。渋川さんのこの記事はだいぶ読ませてもらっていろいろ遊んでいる。

qiita.com

Ryzenが動くPCも持ってるんだけど、起動がめんどくさいせいかこっちのpcでとりあえずコード書くとかが近頃増えてる。 バッテリーの充電間隔が前より減ったり、ベッドサイドに置いておいてとりあえず調べ物したいときに非常に便利、 おかげで久々にブログを書く気が湧いてきたりとこのpcにはいろいろありがとうと言いたい。

その他

この企画のレビューのお手伝いができたて楽しかったですね。 id:t2y-1979 さんお声がけいただいてありがとうございました. Pythonでの型ヒントも色んな人がふつーに使ってくれるようになりつつあり、個人的には嬉しい限りですね。

最後に

なんだかんだで大変な一年ではありましたが今考えると変化に富んでいて色んなことが経験できた年だったなと思います。 私は変化がない生活は考えられないタチなので、来年もいろいろ手を出しつつ生きていきたいなと思います。 何かあったら声がけしていただけるようにしてきたいなと思ってます!

来年やってみたいことはまた来年にでもまとめます。

How to upgrade dokka version 0.10.x to 1.4.x

Kotlinではドキュメント生成ツール(KDoc JavaでのJavadoc的なもの)として Dokkaというツールがあります。 自作のライブラリでdokka 0.10.1を使っていたのですが、気づいたら1.4.xにアップデートしていました。1 Gradleでの書き方も変更があり、だいぶハマったのでまとめておきます。

TL;DR

chore: update dokka to 1.4.20 · masahitojp/bqdatamapper4k@a8a6933 · GitHub

dokka 1.4.xでの変更点

公式ドキュメントでまとまってます。

github.com

/* 0.10.x */    outputDirectory = "$buildDir/javadoc"
/* 1.4.x */     outputDirectory.set(buildDir.resolve("javadoc"))

こんな感じで = で設定していた箇所がsetter methodに変わったりしてるっぽい。 が、実際にコードで使ってるプロジェクトがなかなかなく,じゃーどう書き換えればいいかよくわかりませんでした。 というわけでGithub で検索してなんとなくわかってきました。

// 0.10.xでのjavadocフォルダにhtmlを出力する設定
val dokka by tasks.getting(DokkaTask::class){
    outputFormat = "html"
    outputDirectory = "$buildDir/javadoc"
}

// 1.4.xでの同じ書き方 htmlを出力したいのでdokkaHtmlを使う
tasks.dokkaHtml.configure {
    outputDirectory.set(buildDir.resolve("javadoc"))
}

// for 1.4.x [Option]  Gfm, javadoc, Jekyll向けの設定もある
tasks.dokkaGfm.configure {}
tasks.dokkaJavadoc.configure {}
tasks.dokkaJekyll.configure {}
// for 0.10.x
val dokkaJar by tasks.creating(Jar::class) {
    group = JavaBasePlugin.DOCUMENTATION_GROUP
    description = "Assembles Kotlin docs with Dokka"
    archiveClassifier.set("javadoc")
    // dependsOn(dokka) not needed; dependency automatically inferred by from(dokka)
    from(dokka)
}
// for 1.4.x
val dokkaJar by tasks.creating(Jar::class) {
    group = JavaBasePlugin.DOCUMENTATION_GROUP
    description = "Assembles Kotlin docs with Dokka"
    archiveClassifier.set("javadoc")
    // dependsOn(dokka) not needed; dependency automatically inferred by from(dokka)
    from(tasks.dokkaHtml) // ^でのtasksの設定を入れる
}

参考

github.com

関連ドキュメント

blog.jetbrains.com

先人に感謝。


  1. https://github.com/Kotlin/dokka/releases/tag/v1.4.20 version名にalphatついているのが気になるところ.リライトしたとのことなのでまだ安定版ではないアピール?

Windows10(WSL2)の開発環境

初めに

新しいPCを買ったこともあり、久々にwindowsでの開発環境を構築しようと思いました。 折角なのでWSL2などの最新の環境を試してみました。 作業メモを書いておこうと思います。

WSL2のインストール

今回はこれが目的なので何はなくともこれを導入. この辺を参考にしました。

docs.microsoft.com

一か所はまったところがあったので共有しときます。 wslを入れた後で、the Linux kernel update packageを入れる必要があるのですが、 日本語版のページからリンクがなくなってるっぽいです。英語版のほうにはリンクがあるのでこちらからダウンロードできました。 英語版の方とかなりドキュメントの構成がちがうのでその辺の影響なのかも。なるべく英語のほうを 今は参照したほうがよさそうです。

docs.microsoft.com

Windows Terminal の導入

Windowsの世界の住人になるのでWindows TerminalとPowerShellに移行しました。 この辺りを参考にしました。 Windows TerminalとWSL2のメモ - Qiita

Scoop の導入

chocolateyを入れるのが一般的だと思うんですが、いい機会なのでScoopをいれてみました。 Windows版でのhomwbrew的な感じですかね。 windows10のパッケージ管理 scoopをインストールする | mebee

gitなども簡単に導入できました。

wsl2 intellijの環境の準備

Development under Windows under Linux with WSL2 (IntelliJ) | by JoergF-Ragin | Medium

こちらの記事を参考に入れてみました。 流れとしてはこんなかんじ

  1. wsl2側でintellijをインストール
  2. wsl2側でXserverを導入する
  3. windows側でX client をインストールする

https://rin-ka.net/windows-x-server/#toc14

var.blog.jp

PythonでJSON Linesを扱う

key value
Python 3.8 or later
Last Update 2020/09/06

こんにちは、yaml苦手っ子です。PythonJSON Linesを扱う方法を調べたのでまとめておきます。

JSON Lines とは

JSONを行区切りでファイルに書き込んだものです。

JSON Lines ここによると以下が条件みたいです.

  1. UTF-8 Encoding
  2. Each Line is a Valid JSON Value
  3. Line Separator is '\n'

Python での扱い方

ファイルから読んで、一行ごとにばらして json.load する感じです。 コードで書くとこんな感じです。

import json
from typing import Any, Dict, Iterator, List, TextIO


def filter_not_empty(list: List[str]) -> Iterator[str]:
    return filter(lambda x: len(x) > 0, list)


# TODO: DictではなくTypedDictを使うと型ヒントの恩恵に預かれて便利
def parse_jsonl(fp: TextIO) -> List[Dict[str, Any]]:
    # fileが空行を含むことがあるので念の為フィルターする
    jlines = filter_not_empty(fp.read().splitlines())
    result = [json.loads(jline) for jline in jlines]
    return result


if __name__ == "__main__":
    with open("sample.jsonl", "r") as json_file:
        print(parse_jsonl(json_file))

jqコマンドの代わりにPythonJSON Linesをpretty printする

Python 3.8から json.toolでもJSON Linesに対応しました。こんな感じでつかえます。

json --- JSON エンコーダおよびデコーダ — Python 3.8.5 ドキュメント

$ python3.8 -mjson.tool --json-lines sample.jsonl 
{
    "name": "Gilbert",
    "wins": [
        [
            "straight",
            "7\u2663"
        ],
        [
            "one pair",
            "10\u2665"
        ]
    ]
}
{
    "name": "Alexa",
    "wins": [
        [
            "two pair",
            "4\u2660"
        ],
        [
            "two pair",
            "9\u2660"
        ]
    ]
}
{
    "name": "May",
    "wins": []
}
{
    "name": "Deloise",
    "wins": [
        [
            "three of a kind",
            "5\u2663"
        ]
    ]
}

先人に感謝します。

GitHub ActionsでGradle環境でCIを回してみる

こんにちは、yaml苦手っ子です。GitHub Actions+GradleでCIする方法をまとめます。

key value
Last Update 2020/07/06

Gradle

こちらを参考にすればOK.

docs.github.com

超シンプルに書くとこんな感じ。

name: Java CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      - name: Build with Gradle
        run: ./gradlew test

Cacheを利用する

ここをみると以下のように説明されている。

依存関係をキャッシュしてワークフローのスピードを上げる - GitHub Docs

GitHubホストランナー上のジョブは、クリーンな仮想環境で開始され、依存関係を毎回ダウンロードしなければならず、ネットワークの利用率を増大させ、実行時間が長くなり、コストが高まってしまいます。

なるほど、というわけでキャッシュの使い方をまとめてみる。

actions/cacheを使う

actions cacheを使う方法。 github.com

A repository can have up to 5GB of caches. Once the 5GB limit is reached, older caches will be evicted based on when the cache was last accessed. Caches that are not accessed within the last week will also be evicted. どうやら5GiBの制限があるみたい

READMEにちゃんと設定が書いてある。例としては以下のような感じ

name: Run Gradle unit-test on Push
on: [pull_request, push]
jobs:
  gradle:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-java@v1
        with:
          java-version: 8
      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle
      - name: Build with Gradle
        run: ./gradlew test

gradle pluginでcacheを使う

ちなみに eskatos/gradle-command-action@v1 でもcacheを使うことが可能だったりします。

github.com このActtionではcacheを以下の3つに分けてあるとのこと。(default でwrapper-cacheのみ有効.多分キャッシュの制限が原因)

wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true

以下のような感じになる

github.com

name: "CI"

on: [pull_request, push]


jobs:
  ci:
    name: "CI"
    runs-on: ubuntu-latest
    steps:

      - name: Checkout sources
        uses: actions/checkout@v2

      - name: Setup Java 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Build with Gradle
        uses: eskatos/gradle-command-action@v1
        with:
          dependencies-cache-enabled: true
          configuration-cache-enabled: true
          arguments: test

参考

GradleでのJavaのビルドとテスト - GitHub Docs

GitHub ActionsでGradleの自動ビルド/テストをする [再掲] | Qrunch(クランチ)

Property-Based-TestingのShrinkingをkotlin testで学ぶ

こんにちは永遠のJVM初心者です。 Property-Based-TestingのShrinkingとはなんぞやがよくわかってなかったのでまとめます。

TL;DR

  • Property-Based-TestingにおけるShrinkingとは

Property-Based-TestingにおけるShrinkingとは

blog.johanneslink.net

jqwikの作者のブログを読んでる感じだと以下の点で重要とのこと

Unless you already have an inkling of what the issue might be, the number itself does not give you an obvious hint. Even the fact that it’s rather large might be a coincidence. At this point you will either add additional logging, introduce assertions or even start up the debugger to get more information about the problem at hand.

言ってしまえばpbtによる確認はrandomにgenerateされたものに過ぎないので、失敗した数値だけでてもよくわかんないよねー。
ロギングを追加したり、アサーションを導入したり、デバッガを起動しないと問題の原因はわからんよね。
なので「最も単純な」例をテスト結果で出してほしいよね。
この探索フェーズは、元のサンプルから開始し、それをだんだん小さくして再度propertyを確認しようとするので、Shrinking(縮小)と呼びます。

ということらしい。

Kotestでの例

こんな感じで超簡単な素数判定を考える。 これにPBTをかけると以下のような結果になる

gist.github.com

ここでは 592298304 でfailになったあと最小の数字までshrinkingしてくれているのがわかる。 最終的に4までShrinkingしてくれるおかげで、あぁなるほど4はそもそも素数じゃないなというのがわかってそもそもこのテスト自体が正しくないことが わかる。

KotestでのShrinkingの実装

現状でのInt型のShrinkerは以下のような実装らしい。

github.com

object IntShrinker : Shrinker<Int> {
   override fun shrink(value: Int): List<Int> =
      when (value) {
         0 -> emptyList()
         1, -1 -> listOf(0)
         else -> {
            val a = listOf(abs(value), value / 3, value / 2, value * 2 / 3)
            val b = (1..5).map { value - it }.reversed().filter { it > 0 }
            (a + b).distinct()
               .filterNot { it == value }
         }
      }
}

うーむコメントが少ないのでlistOfのルールがよくわかんない。 多分 value/3 がfailしたときのルールかな...(^見た感じだとfailしたら3でわって言っている様子が見えるかも) ちょっと自力で実装するにはもう少しコード読まないといけなそう

github.com

  • Returns the "next level" of shrinks for the given value, or empty list if a "base case" has been reached.
  • For example, to shrink an int k we may decide to return k/2 and k-1. とのことなので、なにかいい感じでルールを作るということらしい。ただ、そもそも順序関係が必要だよなぁ...

Shrinkingのルール変更

ShrinkingModeで設定可能

こんな感じ

        checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Bounded(50)),Arb.int(2..Int.MAX_VALUE)){
            i ->
            isPrimeNumber(i) shouldBe true
        }

やめたいときは ShrinkingMode.Offを指定すればいいらしい。

github.com

まとめ

PBTのツールではShrinkingが大事というのはよくドキュメントに書かれていたんだけど、実際に使ってみることで重要性がわかりました。 kotestでは今回の例でつかった intだけでなく long, string, listなどでも使用可能なのでbuild-inの方を組み合わせるだけでも十分テストでつかえそうです。 先人に感謝

KotestでProperty-Based-TestingするときのTips

こんにちは、Kotlin初心者です。

TL;DR

この記事は前日のこちらの記事の続きです。が、Property-Based-Testing知ってる方はそのまま読んでもらって問題ありません。

masahito.hatenablog.com

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ひとことでいうと

github.com このドキュメント読むといいよ

forAll() と checkAll()の使い分け

ひとことで行ってしまうと関数の定義が違う

  • forAll : (a, ..., n) -> Boolean
  • check All : (a, ..., n) -> Unit

以上。例はこんな感じ

class PropertyExample: StringSpec({
    "String size" {
        forAll<String, String> { a, b ->
            (a + b).length == a.length + b.length
        }
    }

    "String size2" {
        checkAll<String, String> { a, b ->
            (a + b).length shouldBe  a.length + b.length
        }
    }
})

The checkAll approach will consider a test valid if no exceptions were thrown. なので、ちょっと考え方が違うとも言える。 自分はBooleanを返してほしい派なので大体forAllをつかっている。

generationの回数を増やしたいor減らしたい

標準では1000回生成するそう。増やしたいときには以下のようにする。

class PropertyExample: StringSpec({
   "some test" {
      checkAll<Double, Double>(iterations = 10000) { a, b ->
         // test here
      }
   }
})

失敗したときにもう少しテストを流したい

デフォルトでは失敗したら即打ち切りなんだけど、もっとテスト失敗パターンを出すことも可能。以下のようにする.

class PropertyExample: StringSpec({
   "some flakey test" {
      forAll<String, String>(PropTestConfig(maxFailure = 3)) { a,b ->
         // max of 3 inputs can fail
      }
   }
})

CustomGenerator

Generated values are provided by instances of the sealed class Gen. You can think of a Gen as kind of like an input stream but for property testing. Each Gen will provide a (usually) infinite stream of these values. Kotest has two types of generators - Arb for arbitrary (random) values and Exhaustive for a finite set of values in a closed space.

KotestではGenが基底クラスとして、2種類のジェネレータークラスが提供されている。

To write your own generator for a type T, you just create an instance of Arb or Exhaustive.

というわけで Arb<T> or Exhaustive<T>インスタンスを実装することで自分好みのGeneratorをつくれる

data class Yen(val amount: Long)
test("金額は5000円より高い") {
        val yenArb = arb { rs ->
            val nums = Arb.long(LongRange(5001, 10000)).values(rs)
            nums.map { num -> Yen(num.value) }
        }
        forAll<Yen>(yenArb) { yen1 ->
            yen1.amount > 5000L
        }
}

2変数以上のクラスをGenerateするためのTips

2変数くらいなら zip でOK.

        val userArb = arb { rs ->
            val names = Arb.string(minSize = 1).values(rs)
            val ages = Arb.int().values(rs)
            names.zip(ages).map { (name, age) ->
                User(
                    name.value,
                    age.value
                )
            }
        }

3変数以上なら Arb.bind() がおすすめ

        val orderArb: Arb<Order> =
            Arb.bind(
                Arb.long(0L),
                Arb.string(1, 24)
            ) { a, b -> Order(Item(a, b)) }

別解としてはflatmap+mapでもできたりします(for expression♫)

        val expiredCardArb: Arb<CreditCard> = arb { rs ->
            val nums = Arb.int(0, 9999).values(rs)
            val holders = Arb.string(minSize = 1).values(rs)
            val diffs = Arb.long(1, 24).values(rs)
            nums.flatMap { num ->
                diffs.flatMap { diff ->
                    holders.map { holder ->
                        val numStr = num.value.toString().padStart(4, '0')
                        CreditCard(
                            number = "${numStr}-${numStr}-${numStr}-${numStr}",
                            limit = today.minusMonths(diff.value),
                            holder = holder.value
                        )
                    }
                }
            }
        }

とはいえせっかくなので bindを使ったほうがよさそう。Sequenceで結果返してるのも実行効率考えると良さそう。

// 実装はこんな感じ
fun <A, B, T : Any> Arb.Companion.bind(genA: Gen<A>, genB: Gen<B>, bindFn: (A, B) -> T): Arb<T> = arb {
   val iterA = genA.generate(it).iterator()
   val iterB = genB.generate(it).iterator()
   generateSequence {
      val a = iterA.next()
      val b = iterB.next()
      bindFn(a.value, b.value)
   }
}

kotest/generators.md at master · kotest/kotest · GitHub

まとめ

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ドキュメント読んでてscalaCheckなど先に出ているライブラリをちゃんと意識している印象です。 propertyをちゃんと見出すことができれば結構簡単に値をCustomeGenerator使えるのがいい感じです。 ぜひProperty-Based-Testing使ってみると良いと思います。 コードで変なところあったらぜひツッコミをください。

サンプルコード

github.com