tkzwhr's notes

tkzwhrの技術、中国語などのメモです。

Hyper-VにLubuntuをインストールしてDockerをセットアップする

2020.08.20 追記
今となっては素直にWSL2をインストールしたほうがあらゆる面で有利です。


以前

tkzwhr.hatenablog.com

にてManjaro Linuxをインストールして運用してきたが、 思いの外Hyper-V環境との相性が悪かったため、おとなしくUbuntu系のディストロを入れ直すことにした。 また、開発のためにDockerを使っていきたいので、合わせてインストールした。

続きを読む

糖質制限ダイエットをやってみた

社会人になったときは54kgだった体重が61kgまで増えてしまい、さすがにまずいなぁと思いつつ何もしないままでいたが、親知らずを抜かなければならない状況になってしまったので、どのみち食べたいように食べられなくなるのならと、ついでに糖質制限ダイエットを始めてみた。

続きを読む

Hyper-VにManjaro Linuxをインストールするのが大変だったのでメモ

2020.08.20 追記
今となっては素直にWSL2をインストールしたほうがあらゆる面で有利です。


プライベートPCに開発環境を作るに際して、

  • 慣れているLinuxのエコシステムを使いたい(bashとか、power shellは好きじゃない)
  • Macは開発環境としては良いが性能に比べ費用が割高
  • Win上に直接開発環境を作りたくない、仮想化したい
    • できればWinを中継したくない(WSLは使いたくない、WSL2は良いけど使えるようになるまでまだ1年くらいかかりそう)
    • ただし仮想環境上でGUIIDEを立ち上げると動作が重いのでIDEはWinのものを使いたい

という、わがまま要件を満たすため、スーパーバイザ型の仮想環境であるHyper-V上に、最近トレンドなArch系ディストロのManjaro Linuxを動かすことにした。

実際インストールしようとしたら結構わからなかったので、備忘メモを残しておく。

続きを読む

開発に役立つWebツール集

モックしたい

単純なHTTPサーバがほしい

httpbin.org

GETやPOSTなどのHTTPリクエストをオウム返ししてくれるサービス。

通信部分のアーキテクチャ部分を作るときや、 通信を仮組みしてフロントエンドやモバイルアプリを作るときに便利。 dockerイメージもあるので、カスタマイズしてローカルやECSなどに建てるのも簡単。

ダミーの画像を生成したい

Placehold.jp

URLにパラメータを渡すとそれにあった画像を生成してくれるサービス。

生成された画像はURLでアクセスが可能なため、 フロントエンドを仮組みしているときなど、当てはめる画像を探すのが面倒なときに ここで生成した画像を当て込むことができる。

ちょっと確認したい

正規表現をテストしたい

regular expressions 101

正規表現のパターンを任意の文字列でテストできるサービス。

PHPJavaScriptなど、さまざまな言語の正規表現を検証できる。 単純なマッチングだけでなく、グルーピングでどんな値を 後方参照できるかなども確認できる。

JSONのフォーマット修正と構文チェックをしたい

JSON Formatter & Validator

JSONをフォーマットできるサービス。

JSONを返すAPIを提供しているサービスなどで、 データ量削減のために改行やインデントを消していて見にくい場合がある。 このサービスで整形を行うことができる。

あるエポック秒が指し示す時間を知りたい/ある時間のエポック秒が知りたい

Epoch Converter

エポック秒と時間の相互変換を行うサービス。

出力がエポック秒になっている場合、それがいつなのかわかりづらいが、 このサービスで可読性の高い時間表記に変更することができる。

データ変換・生成したい

URLエンコード/デコードしたい

UrlEncode.net

URLをエンコード/デコードできるサービス。

Shift_JISUTF-8などでエンコードされた文字が含まれるURLをデコードしたり、 その逆を行うことができる。

CSVJSONにしたい

CSV to JSON

CSVからJSONを生成するサービス。

集計データなどはCSVになっていることが多いが、 プログラムではCSVよりJSONをパースするほうが手軽なことが多いため、 JSONに変換しておくと取り扱いがしやすい。

JSON Web Tokenのパースをしたい

JSON Web Token (JWT)

JWTの文字列からペイロードを確認したり、ペイロードからJWTを生成できるサービス。

認証にJWTを使用している場合に、ペイロードが正しいか確認する場合に便利。

JSON Schemeを書きたい

JSON Schema Tool

JSON Schemaを編集できるサービス。

ちょっとPlaygroundで遊びたい

Scalaを試したい

Scastie

Dartを試したい

DartPad

Scalaの等価性

ScalaJavaの違い

両言語の取扱いの違いについて下記にまとめる。

Scala Java
値の比較 == ==
オブジェクトの比較(値等価) == equals
オブジェクトの比較(参照等価) eq ==
オブジェクトの比較(ユーザ定義等価) == (override equals) override equals

等価性実装のポイント

class A(val value: String) {
  var mutatedValue: String /* 3. */
  override def equals(x: Any /* 1. */) = x match {
    case xx: A => value == xx.value
    case _ => false
  }
  override def hashCode = value.## /* 2. */
}
  1. Any#equalsをオーバーライドする
    • 引数の型はクラスの型ではなくAnyになる
  2. hashCodeは常にequalsで使っている値をすべて使って常に同じ計算結果となるようにする
  3. ミュータブルなプロパティをequalsに使用してはいけない
  4. 下記条件を満たすようにする
    • x.equals(x) == true // x is not null
    • x.equals(y) == y.equals(x) // x, y is not null
    • when x.equals(y) == y.equals(z), x.equals(z) == true // x, y, z is not null
    • x.equals(null) == false // x is not null
  5. 冪等である

継承関係にあるクラスでのポイント

継承関係にあるクラスでの等価性は、基本的に厳格にしたほうが良い場合が多い。

例えば、下記のようなTextAttributedTextのクラスでは、比較結果がtrueになるパターンをあれこれ考えるより、相互に比較結果がfalseになるように実装したほうが比較時の違和感が少ない。

class Text(val value: String) {
  override def equals(x: Any) = x match {
    case xx: Text => canEqual(x) && value == xx.value
    case _ => false
  }
  override def hashCode = value.##
  def canEqual(x: Any) = x.isInstanceOf[Text]

  // 一部の等価性を比較したい場合は`==`ではなく独自にメソッドを実装したほうがわかりやすい
  def equalsText(x: Text) = value == x.value
}

class AttributedText(override val value: String, val attributes: Map[String, Any]) extends Text(value) {
  override def equals(x: Any) = x match {
    case xx: AttributedText => canEqual(x) && super.equals(x) && attributes == xx.attributes
    case _ => false
  }
  override def hashCode = (value, attributes).##
  override def canEqual(x: Any) = x.isInstanceOf[AttributedText]
}

上記のようにcanEqualを実装し、インスタンス判定を条件に加えることで実現できる。 また、こうしておくと、無名サブクラスでも正しく判定されるようになる。

val text = new Text("test")
val anotherText = new Text("sample") { override val value: String = "test" }
println(text == anotherText)
// ==> true

Scalaの変位パラメータ

前提

前提として、計数可能な性質を表すCountableを定義する。 Countableは説明と数量を保持する。

trait Countable {
  val description: String
  def number: Int
}

ここでは、上記の性質をテーブルゲームに適用し、具体化したものとして、将棋の駒数、麻雀牌の数をモデリングしてみる。

abstract class TableGame extends Countable {
  val players: Int
}

case object Shogi extends TableGame {
  override val description = "将棋の駒数"
  override val number = 40
  override val players = 2
}

case object Mahjong extends TableGame {
  override val description = "麻雀牌の数"
  override val number = 136
  override val players = 4
}

上限境界で与えられる型を限定する

Countableの数量が最大であることを表す型Maxを定義してみる。 Maxは型パラメータTを持っており、最大値を持つオブジェクトをvalueとして保持する。

また、与えられた値Tを自身と比較し、より大きい値を持つMaxを返却するメソッドmaxも実装する。

case class Max[T](initial: T) {
  val value = initial
  def max(x: T): Max[T] = if (x.number > value.number) Max(x) else this
}

ただし、このままだとTCountableを継承しない型をとれるため、maxコンパイルエラーとなってしまう。

value number is not a member of type parameter T

そこで、TCountableの性質をもつことを示すため、上限境界を設定する。

- case class Max[T](initial: T) {
+ case class Max[T <: Countable](initial: T) {  // <: が上限境界の指定
    val value = initial
    def max(x: T): Max[T] = if (x.number > value.number) Max(x) else this
  }

こうすることで、TCountableのサブクラスのインスタンスまたはミックスインされたオブジェクトに限定することができるため、numberが参照できるようになる。 次のコードを実行すると結果が表示される。

val max = Max(Shogi).max(Shogi)
println(s"${max.value.description}は${max.value.number}です")
// ==> "将棋の駒数は40です。"

maxをより有用な処理にするために

先ほどの例では、同じものを比較しており、意味を感じられるものではなかった。 では次のコードを実行するとどうなるだろうか。

val max = Max(Shogi).max(Mahjong)
println(s"${max.value.description}は${max.value.number}です")

少しは意味がありそうなコードになったが、実際に実行しようとするとコンパイルエラーとなる。

type mismatch;
    found : Mahjong.type
    required: Shogi.type

Max(Shogi)インスタンス型はMax[Shogi]なので、maxが受け取る引数の型も、返却するMaxの型パラメータもShogiを要求しているのである。これに対応するためにはCountableを取り扱うようにすれば良い。

  case class Max[T <: Countable](initial: T) {
    val value = initial
-   def max(x: T): Max[T] = if (x.number > value.number) Max(x) else this
+   def max(x: Countable): Max[Countable] = if (x.number > value.number) Max(x) else this
  }

しかし、依然としてコンパイルエラーとなる。

type mismatch;
    found : Max[T]
    required: Max[Countable]
Note: T <: Countable, but class Max is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)

これは、maxの定義として、Max[Countable]を返却する宣言なのにMax[T]を返却しているためである。

非変(invariant)と共変(co-variant)

このエラーに対処するためには非変と共変について理解しておく必要がある。

B extends Aであるとき、C[T]についてC[B] extends C[A]を満たすときのT共変という。 一方C[B]C[A]をそれぞれ独立した型として扱うときのT非変という。 Scalaでは型パラメータは基本的に非変である。そのためエラーとなったのである。

ところで、ここでMax[T]を返すことに何か問題はあるだろうか。 Tについては下限境界を設定しているため、Countableの性質を備えていることが自明であるため、Max[T]Max[Countable]のサブ型とみて問題はないだろう。 そのため、ここでは型パラメータを共変に変更することでMax[T] extends Max[Countable]であることを明示する。そうすれば、Max[Countable]を要求する場面で、より具体的なMax[T]を返却することができるようになる。

- case class Max[T <: Countable](initial: T) {
+ case class Max[+T <: Countable](initial: T) { // + が共変の指定
    val value = initial
    def max(x: Countable): Max[Countable] = if (x.number > value.number) Max(x) else this
  }

これで、maxの結果は常にMax[Countable]として得られるのである。

下限境界を使ってmaxをもっと便利に

maxにはもう少し改善の余地がある。

val max = Max(Shogi).max(Mahjong)

上記の変数maxの型はどうなるだろうか。 ShogiMahjongしか渡していないため、結果はMax[Shogi]Max[Mahjong]のどちらかにしかならないはずである。 つまり、Max[TableGame]であることは確実である。 ところが、実際にはMax[Countable]になってしまう。

いつでもCountableというのは型を決めすぎていて少し扱いが難しい。 今回の例で言えばMax[TableGame]として取り扱いたいところである。

これは新たな型パラメータと下限境界を導入することで改善可能である。

  case class Max[+T <: Countable](initial: T) {
    val value = initial
-   def max(x: Countable): Max[Countable] = if (x.number > value.number) Max(x) else this
+   def max[U >: T <: Countable](x: U): Max[U] = if (x.number > value.number) Max(x) else this // >: が下限境界の指定
  }

少しややこしいが、U >: T <: Countableと書くことで、このUの取るクラスの継承関係が下図の範囲であることを示している。

この状態で次のコードを実行すると、TableGameとして扱えていることがわかる。

val max = Max(Shogi).max(Mahjong)
println(s"${max.value.description}は${max.value.number}です。プレイヤー数は${max.value.players}です。")
// ==> "麻雀牌の数は136です。プレイヤー数は4です。"

反変(contravariant)

今回は取り上げなかったが、非変、共変の他に反変というものもある。 B extends Aであるとき、C[T]についてC[A] extends C[B]を満たすときのTを反変という。

反変の指定は-で行う。

trait Channel[-T] {
  def output(x: T)
}

Scalaパターンマッチ集

定数パターン

expr match {
  case 1 => println("one")
  case _ => println("other")
}

変数パターン

expr match {
  case a => println(s"expr is $a")
  case _ => println("none")
}

型付きパターン

expr match {
  case a: MyClass => println("hit!")
  case _ => println("miss...")
}

型パラメータは消されるので、下記のようなことはできない。

expr match {
  case a: MyValue[Int] => println(s"my value is ${a.read}")
  case b: MyValue[String] => println(s"my value is '${a.read}'")
  _ => println("none")
}

コンストラクタパターン

expr match {
  case MyCaseAdd(l, r) => println(s"$l + $r = ${l + r}")
  case _ => println("none")
}

MyCaseAddはケースクラスである必要がある。

シールドクラス

基底クラスにシールドクラスを利用することで、全ケース網羅のチェックができる。

sealed abstract class Expr
case object Even extends Expr
case object Odd extends Expr

expr match {
  case Even => println("even")
}
// ==> warning: match is not exhaustive!
//     missing combination            Odd

シーケンス、タプルパターン

expr match {
  case List(a, b, _*) => println(s"$a, $b, and more...")
  case (a, b) => println(s"$a, $b")
  case _ => println("none")
}

_*で可変長シーケンスとのマッチが可能。

パターン束縛

expr match {
  case p @ MyPoint(x, y) => println(s"${p.invert}")
}

抽出子を使ったパターンマッチ

抽出子で柔軟なパターンマッチを行うを参照

パターンガード

ifでパターンマッチにさらに条件をつけられる。

expr match {
  case n if n % 2 == 0 => println(s"even: $n")
  case n => print(s"odd: $n")
}

ケースの変数展開

val (a, b) = expr

部分関数としてのケース

def twice(val: Option[Int]): Int = {
  case Some(n) => n * 2
  case _ => 0
}

抽出子で柔軟なパターンマッチを行う

unapply(a: T): Option[U]を実装したオブジェクトを抽出子と呼ぶ。

基本的にコンストラクタパターンにおけるパターンマッチはケースクラスに対して行うものであるが、抽出子を実装することで、ケースクラスでないオブジェクト(上記の例では数値)に対してもパターンマッチを適用することができる。

下記は1~100までの中で平方数を探すプログラムである。

import scala.math.sqrt

object Square {
  def apply(num: Int) = num * num
  def unapply(num: Int): Option[Int] = {
    if (sqrt(num) % 1 == 0) Some(sqrt(num).toInt) else None
  }
}

for (i <- 1 to 100) i match {
  case Square(x) => println(s"$i is a square number of $x.")
  case _ => println(s"$i is not a square number.")
}

返す値の数が可変長になる場合を考慮して、unapplySeq(a: T): Option[Seq[U]]というものも用意されている。

下記はタプル2から公約数を求めるプログラムである。

import scala.math._

object CommonDivisor {
  def unapplySeq(num: (Int, Int)): Option[Seq[Int]] = {
    val gcd = grand(num._1, num._2)
    if (gcd == 1) {
      Some(Seq(1))
    } else {
      val divisorsOfGcd = for {
        i <- 1 to ceil(sqrt(gcd)).toInt
        if gcd % i == 0
      } yield {
        if (i > 1 && i < sqrt(gcd)) Seq(i, gcd / i) else Seq(i)
      }
      Some(divisorsOfGcd.flatten.sorted :+ gcd)
    }
  }
  
  private def grand(a: Int, b: Int): Int = {
    val (higher, lower) = (max(a, b), min(a, b))
    if (lower == 0) higher else grand(lower, higher % lower)
  }
}

(120, 80) match {
  case n @ CommonDivisor(m @ _*) => println(s"All common divisors of $n are ${m.mkString(", ")}.")
}
// ⇒ "All common divisors of (120,80) are 1, 2, 4, 5, 8, 10, 20, 40."