Scala で Builder Pattern

ScalaJava like なBuilder Patternを書く機会は自分の場合あまり無い(Static Factoryで事足りることが多い)のですが、少し古いJavaのプロダクトのリファクタをした際にBuilder Patternで生成処理を書き直したので、Scalaの場合はどうなるのか見ていこうと思います。

Generalized Typed Constraint の勉強にも一部なるところもあったりします。

JavaにおけるBuilder Pattern

JavaにおけるBuilder Pattern は GoF と Effective Java が有名かなと思います。Builder Patternの説明としては、Effective Javaの方が使い所とかが個人的には分かりやすい気がします。コードとしても、Effective Javaの書き方の方が、コードが散らばらないので好みですね。※Effective Javaはクラス内に `static class` として、`Builder` クラスを定義するが、GoFの方は、`Person` クラスと別に、`PersonBuilder` のようなクラスを作る。

JavaにおけるBuilder Pattern を使いたくなるときと、BuilderPattern以前の書き方

Builder Patternが使いたくなるのは、必須では無いけれど場合に応じて外から値を渡してオブジェクトを生成したい時、オプショナルパラメータが多い時に使いたくなります。オプショナルパラメータが多い時に選択される伝統的なパターンとして、Effective Javaではテレスコーピング・コンストラクタ(Telescoping Constructor)とJavaBeansパターンが紹介されていました。

テレスコーピング・コンストラクタは、単純にコンストラクタを複数用意するパターンです。

6個のパラメータでこれなので、コンストラクタがいくつあっても足りないですし、上から順番にしか対応できてないので、例えば、`carbohydrate` は設定したいけど、他のパラメータはデフォルトパラメータで良いとかには対応できません。

私がこの業界に入った時にはすでにJavaBeansパターンは廃れてましたが、古いプロダクトだとやはりよく使われています。JavaBeans パターンは setter を使って行うパターンです。そもそも不変でないので、バグが入り込みやすいですし、Scalaではまず出会わないですね。でもやっぱり古いJavaのプロダクトだとよく見ます。setterを排除したい時に、Builder Patternに置き換えるのはリファクタとしてはやりやすいなと思います。

Scalaを使っていると、ほぼ直面しない、値がミュータブルな状態なってしまうのがJavaBeansパターンの最悪の欠点ですね。最新のJavaは知らないですが、基本的に値がミュータブルになってるJavaといえど、イミュータブルの方が扱いやすいことには変わりありません。しかし、JavaBeansパターンはテレスコーピングパターンよりも柔軟です。値変えたいもののsetterのみを呼び出せば良いわけなので。

Builder Pattern (Java

テレスコーピングパターンも、JavaBeansパターンもどちらも使いづらい。そこで使われるのがBuilder Patternです。(Effective Java ver.)

呼び出しのところを見ればわかるように、Builderからメソッドチェーンのように生成処理が書けます。

ScalaにおけるBuilder Pattern

Javaの方を見てわかるように、基本的にはオプショナルパラメータはデフォルト値で良くて、時々オプショナルパラメータの値を外部から渡したい。そしてそのようなオプショナルパラメータが多い時にBuilder Patternは活躍します。

case class のデフォルトパラメータを使う

Scalaでデフォルトパラメータを使う場合は、すごく簡単にかけます。

JavaのBuilder Patternとこれで同じことができています。コード量、可読性、段違いですね。

呼び出しも、オプショナルのところは名前付きで渡してあげれば自由に書けます。

Java likeに書いてみる

上のように簡単に書けるのはわかりましたが、Javaっぽく書くとどうなるのかも簡単に見ておきます。

`static class` だった `Builder` がScalaの場合はコンパニオンオブジェクトに移動するくらいですね。Javaと違って面倒なだけで、特に恩恵がないです。

Generalized type constrains

なんならここからが本題。Generalized type constrains を利用してさらにレベルアップさせます。

Scala 系のブログを漁ってたら少し古い下記の記事を見つけて知りました。コップ本にも、多分載って無い内容ですね。なので使い道とか調べるの難しい。。

yuroyoro.hatenablog.com

上記の記事に記載がありますが

Scala2.8から、Predefに<:<とか=:=とかが定義されていて、これなんだろ?とずーっと疑問だった訳ですよ。で、ついったーで質問投げてたらやっと理解できました。

"generalized type constraints"というヤツで、型パラメータに与えられた型が、特定の条件を満たす場合にのみ呼び出せるメソッドを定義できるというものです。しかもコンパイル時に静的にチェックされる!! これはスゴい!!

https://yuroyoro.hatenablog.com/entry/20100914/1284471301

type-safe builder

すこし話は戻って、オブジェクトの生成時に、例えば特定の順序で初期化する必要があったりすることがあります(あるそうです。自分はそういうシーンにはまだ遭遇したことがない)。これまでの Builder Pattern の実装では、実行時にしかそのことを検証できませんでしたが、type-safe builder と呼ばれる実装方法を使うと、コンパイル時に検証できるようになります。

gist.github.com

これで、例えば setFirstName を抜いて build しようとしてもIntelliJ なら静的解析の段階でエラーを出してくれますし、当然コンパイルエラーにもなります。順序を保証したい時とかに便利ですね。

最後に

Java like なBuilder PatternをScalaで使うことは基本的にはそんななく、多くはstatic factoryや、case class のデフォルトパラメータで十分だろうなと思います。type-safe builder に関してなかなかこれが必要な制約下になることはあんまり想像つきませんが、有用かなと思います。

Haskell 勉強メモ9

セクション

中置関数に対して、セクションという機能を使って部分適用をすることができる。

divideByTen :: (Floating a) => a -> a
divideByTen = (/10)

*Main> divideByTen 30
3.0

これは 30 / 10 と同義。

高階関数

関数を受け取り、2回適用する関数を書いてみる。先にScalaで書いてみよう。

def applyTwice[T](f: T => T)(x: T): T = f(f(x))

applyTwice { v: Int => v * 2 }(10) // 40

これを Haskell で書くとこうなる。

applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

*Main> applyTwice (\a -> a * 2) 10
40

`(\a -> a * 2)` は Haskell無名関数

zipWith

標準ライブラリの zipWith

まずはHaskell標準の zipWith の挙動を確認する。

*Main> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith は 2つの引数をとって1つの値を返す関数と、2つのリストを引数に取り、2つのリストの各要素に関数を適用することで、1つに結合する。

*Main> zipWith (\a b -> a + b) [1,2,3] [4,5,6]
[5,7,9]

Scalaには zipWith に相当するものはないが、zip メソッドが collection 系には実装されている

List(1, 2, 3) zip List(2, 3, 4) // res2: List[(Int, Int)] = List((1,2), (2,3), (3,4))

2つのリストを引数にとって、前から順に同じ位置にある要素で構成されたタプルのリストを作って返す。これに map を適用してあげれば、先程の haskellzipWith 相当のことが可能だ。

List(1, 2, 3) zip List(2, 3, 4) map { case (a, b) => a + b } // res2: List[Int] = List(3, 5, 7)

zipWith を実装してみる。※ 途中で、gist を使えば haskell もsyntax highlight いけることに気づいたので少し行数があるやつは試しに。

Scalaで同じようなことを書くとこんな感じ。最初の zipmap を連携させる方が読みやすいし書きやすい。

flip

普段Scalaを使っていて、さっきの zipWith なんかは似たようなもの、似たようなことを Scala でもやったことがあるが、これはhaskell本で初めてみる。

flip は関数を引数にとり、引数が入れ替わった関数を返す。※正直、何が嬉しいのか全く分からない。

Main> :t flip
flip :: (a -> b -> c) -> b -> a -> c
*Main> zip [1,2,3,4,5] "hello"
[(1,'h'),(2,'e'),(3,'l'),(4,'l'),(5,'o')]
*Main> flip zip [1,2,3,4,5] "hello"
[('h',1),('e',2),('l',3),('l',4),('o',5)]

zip は1つ目の配列と2つ目の配列の同じ位置にある要素で作ったタプルのリストを返す関数だ。1つ目の通常の zip では左の配列にあったものがタプルの左に、右の配列にあったものがタプルの右側にあるのが分かる。

flip を適用した zip はそれが逆になっている。:t で見てみるとよく分かるが、引数が入れ替わっていることが分かる。

*Main> :t zip
zip :: [a] -> [b] -> [(a, b)]
*Main> :t flip zip
flip zip :: [b] -> [a] -> [(a, b)]

自分で実装すると下記のようになる。

木勉強メモ

木構造の取扱はプログラマなら基本なのかもだけれど、アルゴリズムとデータ構造をちゃんと勉強したことなかったので、必要ベースで勉強していく。

用語

用語説明
根(root)親を持たないノード。1つの木に対して1つ根。
辺(edge)親から子へのリンク。
葉(leaf)子を持たないノード。
兄弟(siblings)親が同じ子たち。
祖先(ancestor)p → q への経路がある場合、pをqの祖先と呼ぶ。
子孫(descendant)p → q への経路がある場合、qをpの子孫と呼ぶ。
レベル(level)ある木の深さの全ノードの集合を木のレベルと呼ぶ。
(ノードの)深さ(depth)根からノードまでの経路の長さ。
(ノードの)高さ(height)ノードから、一番深いノードまでの経路の長さ
木の高さ(Height of the tree)
木の深さ(depth of the tree)
木の全ノードの最大の高さ。
ノードのサイズ自分自身を含めた子孫の数
木に関する用語

二分木

どのノードも、子を、1または2しか持たない時、二分木(binary tree)と呼ぶ。

二分木の構造はデータフィールドの脇に左右の子を指すリンクを持たせる形。参考資料に合わせてCだと下記のようになる。

struct BinaryTreeNode {
    int data;
    struct BinaryTreeNode *left;
    struct BinaryTreeNode *right;
};

Scalaの場合は、leftright プロパティとして自身と同じ型の値を持つと考えて。

case class BinaryTreeNode(
    data: Int,
    left: Option[BinaryTreeNode],
    right: Option[BinaryTreeNode]
)

二分木の演算

二分木には基本演算と補助的演算が存在する

基本演算

  • 木に要素を挿入
  • 木から要素を削除
  • 要素を探索
  • 木を横断

補助的演算

  • 木のサイズを求める
  • 木の高さを求める
  • 最大和をもつレベルを求める
  • あるノード対の最小共通祖先(LCA)を求める。など。

二分木を横断する

前順序横断

  1. 根を訪問
  2. 前順序で左部分木を横断
  3. 前順序で右部分木を横断

Cでの実装(Cちゃんと書いたことないから変かもしれない)

#include &lt;stdio.h&gt;

struct BinaryTreeNode {
    int data;
    struct BinaryTreeNode *left;
    struct BinaryTreeNode *right;
};

void PreOrder(struct BinaryTreeNode *root) {
    if (root) {
        printf("%d", root-&gt;data);
        PreOrder(root-&gt;left);
        PreOrder(root-&gt;right);
    }
}

struct BinaryTreeNode makeBinaryTreeNode(int data, struct BinaryTreeNode *left, struct BinaryTreeNode *right) {
    struct BinaryTreeNode temp;
    temp.data = data;
    temp.left = left;
    temp.right = right;
    return temp;
}

int main() {

    struct BinaryTreeNode node7 = makeBinaryTreeNode(7, NULL, NULL);
    struct BinaryTreeNode node6 = makeBinaryTreeNode(6, NULL, NULL);
    struct BinaryTreeNode node5 = makeBinaryTreeNode(5, NULL, NULL);
    struct BinaryTreeNode node4 = makeBinaryTreeNode(4, NULL, NULL);
    struct BinaryTreeNode node3 = makeBinaryTreeNode(3, &amp;node6, &amp;node7);
    struct BinaryTreeNode node2 = makeBinaryTreeNode(2, &amp;node4, &amp;node5);
    struct BinaryTreeNode node1 = makeBinaryTreeNode(1, &amp;node2, &amp;node3);

    PreOrder(&amp;node1);

    return 0;
}

// 実行結果
// 1245367</pre>

Scalaで同じことをする。

def preOrder(root: BinaryTreeNode): Unit = {
  print(root.data)
  root.left.foreach(preOrder)
  root.right.foreach(preOrder)
}

val node7 = BinaryTreeNode(7, None, None)
val node6 = BinaryTreeNode(6, None, None)
val node5 = BinaryTreeNode(5, None, None)
val node4 = BinaryTreeNode(4, None, None)
val node3 = BinaryTreeNode(3, Some(node6), Some(node7))
val node2 = BinaryTreeNode(2, Some(node4), Some(node5))
val node1 = BinaryTreeNode(1, Some(node2), Some(node3))

preOrder(node1) // 1245367

もう少し、Scalaらしく書いて見る。Scalaで二分木を表現するならば、sealed trait を用いて

sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]</pre>

のように表現できる。trait Tree のメソッドとして preOrder を定義すると

sealed trait Tree[+A] {
  def preOrder: Unit = this match {
    case Leaf(value) =&gt; print(value)
    case Branch(value, left, right) =&gt;
      print(value)
      left.preOrder
      right.preOrder
  }
}

val leaf7 = Leaf(7)
val leaf6 = Leaf(6)
val leaf5 = Leaf(5)
val leaf4 = Leaf(4)
val branch3 = Branch(3, leaf6, leaf7)
val branch2 = Branch(2, leaf4, leaf5)
val branch1 = Branch(1, branch2, branch3)

branch1.preOrder // 1245367
branch2.preOrder // 245
branch3.preOrder // 367</pre>

全要素を走査できるならば、map が定義できそう。

sealed trait Tree[+A] {
  ...

  def map[B](f: A =&gt; B): Tree[B] = this match {
    case Leaf(value) =&gt; Leaf(f(value))
    case Branch(value, left, right) =&gt; Branch(f(value), left.map(f), right.map(f))
  }
}

branch1.map(v =&gt; v * 10).preOrder // 10204050306070</pre>

foreach を定義してあげれば、preOrder 不要。

sealed trait Tree[+A] {
  ...

  def foreach[U](f: A =&gt; U): Unit = {
    this match {
      case Leaf(value) =&gt; f(value)
      case Branch(value, left, right) =&gt;
        f(value)
        left.foreach(f)
        right.foreach(f)
    }
  }
}

二分木で要素を探索する。C言語版。要素が存在したら、1を返し、無ければ0を返す。

int FindInBinaryTreeUsingRecursion(struct BinaryTreeNode *root, int data) {
    int temp;
    if (root == NULL) return 0;
    else {
        if (data == root-&gt;data)
            return 1;
        else {
            temp = FindInBinaryTreeUsingRecursion(root-&gt;left, data);
            if (temp != 0) return temp;
            else return (FindInBinaryTreeUsingRecursion(root-&gt;right, data));
        }
    }
}

これを参考にScala版。引数に A を引数にとって Boolean を返す関数をとり、Option[A] を返す。左から順に調べる。

def find(f: A =&gt; Boolean): Option[A] = {
    this match {
      case Leaf(value) =&gt; if (f(value)) Some(value) else None
      case Branch(value, left, right) =&gt;
        if (f(value)) Some(value)
        else {
          val temp = left.find(f)
          if (temp.isDefined) temp else right.find(f)
        }
    }
  }

参考

Haskell 勉強メモ8

高階関数

Haskell は関数を引数にとったり、返り値として関数を返すことができる。このような関数を高階関数と呼ぶ。Scalaにもある。

カリー化関数

Haskellの全ての関数は、引数を1つだけ取る。今まで、複数引数を取っているように見えたものは全てカリー化された関数だ。

Prelude> :t max
max :: Ord a => a -> a -> a

Prelude> max 4 5
5

max 関数は2つの引数を取り、大きい方を返す関数のように見えるが実際はカリー化された関数。まず、max 4 が適用され、max 4 は1つの値を引数にとる別の関数を返す。返ってきた関数に対して、5が適用されるという流れだ。

Prelude> let max' = max 4
Prelude> :t max'
max' :: (Ord a, Num a) => a -> a
Prelude> 

Scalaもカリー化できる。

def max(a: Int)(b: Int): Int = if (a >= b) a else b

max(4)(5) val res0: Int = 5
val max2 = max(4)(_) // val max2: Int => Int = <function>

Scalaの場合はHaskellほど素直ではなく、適用しない部分には _ を入れる必要があるようだ。また、関数部分適用はカリー化されていなくてもできて

def multiThree(x: Int, y: Int, z: Int): Int = x * y * z

val m2 = multiThree(3, _, _) // val m2: (Int, Int) => Int = <function>
m2(2, 4) // val res2: Int = 24

のように書くこともできる。

今度は、Int を引数にとって100と比較する関数を作る。これは単純に書くとこうだが

compareWithHundred :: Int -> Ordering
compareWithHundred x = compare 100 x

*Main> compareWithHundred 99
GT

実際には下記のように書くことができる

compareWithHundred :: Int -> Ordering
compareWithHundred = compare 100

compare 100 が引数を一つとって、Ordering を返す関数であるからだ。

Apache POI 調査

Excelを操作したいみたいな状況は割とよくある。Pythonとかのスクリプト言語だと結構対処法をが見つかるけど、Scalaだと、Java資産のApache POIくらいしかない上に情報もそんなにない(poi.scala のようなラッパーライブラリもあるようだけど、プロダクションで使うには早計感ある)。Apache POIの使い方を調べて、人並みに使えるようになろうと思う。参考記載のJavaでのApache POIの使い方を元に、Scalaで書いて感じを掴む。

準備

必要なライブラリを build.sbt に追記する。

libraryDependencies ++= Seq(
      "org.apache.poi" % "poi"       % "5.0.0",
      "org.apache.poi" % "poi-ooxml" % "5.0.0"
)

Excelファイルの作成

build.sbt の準備ができたら、Excelファイルを作成していく。

XLS ファイルの作成

今だと使う機会は少ないかもしれないが一応。XLSファイルを扱うのは、上記のライブラリのうち poi の方だ。

package dev.tchiba.researchApachePoi

import org.apache.poi.hssf.usermodel.HSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLS {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xls"
    val workbook = new HSSFWorkbook() // XLSにはHSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

XLSX ファイルの作成

XLSXファイルを作成するには、build.sbt 記載のライブラリのうち、後者の poi-ooxml が必要だ。処理はほとんど同じで、生成するインスタンスのクラスのみが異なる。

package dev.tchiba.researchApachePoi

import org.apache.poi.xssf.usermodel.XSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLSX {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xlsx"
    val workbook = new XSSFWorkbook() // XLSX を生成する場合は、XSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

Excelファイルにデータを書き込む

空のExcelファイルが作れても一ミリも嬉しくないので、データを書き込んでいこう。サンプルに下記のようなデータモデルを用意する。

case class Person(
    firstName: String,
    lastName: String,
    birthDay: LocalDate,
    email: String,
    phoneNumber: String,
    married: Boolean
)

これをExcelに書き込むクラスを用意しよう。参考がJavaなので、あんまり Scala っぽくないけど、下手に外れると詰まるから諦める。

package dev.tchiba.researchApachePoi

import models.Person

import org.apache.poi.ss.usermodel.{Sheet, Workbook}
import org.apache.poi.xssf.usermodel.{XSSFSheet, XSSFWorkbook}

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

class PersonExcelWriter {

  def write(fileName: String, personList: List[Person]): Unit = {
    val workbook = prepareWorkbook(personList)
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }

  private def prepareWorkbook(personList: List[Person]): Workbook = {
    val workbook: XSSFWorkbook = new XSSFWorkbook()
    val personSheet: XSSFSheet = workbook.createSheet("Persons")
    prepareHeader(personSheet)
    prepareTable(personSheet, personList)
    workbook
  }

  private def prepareHeader(personSheet: Sheet): Unit = {
    // ヘッダー行を作成する
    val headerRow = personSheet.createRow(0)

    // セルを作成する
    val firstNameHeaderCell = headerRow.createCell(0)
    val lastNameHeaderCell  = headerRow.createCell(1)
    val birthdayHeaderCell  = headerRow.createCell(2)
    val emailHeaderCell     = headerRow.createCell(3)
    val phoneNumberCell     = headerRow.createCell(4)
    val marriedHeaderCell   = headerRow.createCell(5)

    // セルに値をセットする
    firstNameHeaderCell.setCellValue("First name")
    lastNameHeaderCell.setCellValue("Last name")
    birthdayHeaderCell.setCellValue("Birthday")
    emailHeaderCell.setCellValue("Email")
    phoneNumberCell.setCellValue("Phone number")
    marriedHeaderCell.setCellValue("Married")
  }

  private def prepareTable(personSheet: Sheet, personList: List[Person]): Unit = {
    personList.zipWithIndex.foreach { case (person, index) =>
      // 行を作成する
      val row = personSheet.createRow(index + 1)

      // セルを作成する
      val firstNameCell   = row.createCell(0)
      val lastNameCell    = row.createCell(1)
      val birthdayCell    = row.createCell(2)
      val emailCell       = row.createCell(3)
      val phoneNumberCell = row.createCell(4)
      val marriedCell     = row.createCell(5)

      // セルに値をセットする
      firstNameCell.setCellValue(person.firstName)
      lastNameCell.setCellValue(person.lastName)
      birthdayCell.setCellValue(person.birthDay.toString)
      emailCell.setCellValue(person.email)
      phoneNumberCell.setCellValue(person.phoneNumber)
      marriedCell.setCellValue(person.married)
    }
  }
}

手順としては

  1. ワークブックを用意する
  2. シートを用意する
  3. カラムにデータを埋めていく
  4. 最後に、ワークブックをファイルに保存する

となる。

これを使う処理を書こう。

package dev.tchiba.researchApachePoi

import models.Person

import java.time.LocalDate

object Main {
  def main(args: Array[String]): Unit = {
    val person1 = Person(
      firstName = "太郎",
      lastName = "田中",
      birthDay = LocalDate.of(1990, 2, 2),
      email = "taro.tanaka@xxx.com",
      phoneNumber = "090XXXXXXX",
      married = true
    )

    val person2 = Person(
      firstName = "花子",
      lastName = "佐藤",
      birthDay = LocalDate.of(1992, 9, 9),
      email = "hanako.sato@xxx.com",
      phoneNumber = "090YYYYYYY",
      married = false
    )

    val personList = List(person1, person2)

    val writer = new PersonExcelWriter()
    writer.write("person.xlsx", personList)
  }
}

実行して出力されたファイルを開いてみると、(私のPCにはExcel入っていないので、Numbersを利用)、想定通りに値が入っていることが分かる。

参考

Apache POI に関する体系的な情報が探しづらいなか、下記の本がKindle Unlimited にあったのが救いだった。

Haskell勉強メモ 7

クイックソート

アルゴリズムの定番クイックソート

といっても、私はアルゴリズムをちゃんと勉強したことないので名前しか知らないのだけれど。。

これを関数型ではどうやって解くのかを見ていく。

が、先に手続き型ではどうやるのか。アルゴリズム本を見ながら慣れてるScalaで手続き的に書いてみる。

クイックソートとは

配列を分割して、それぞれ再帰的に解いてまとめる分割統治法にのっとったアルゴリズム

適当にpivotを選び出し、配列全体を「pivot未満のグループ」と「pivot以上のグループ」に分割してそれぞれ再帰的に解く。

Scalaで手続き的に書く場合(参考

def quickSort(xs: Array[Int]): Array[Int] = {
  def swap(i: Int, j: Int): Unit = {
    val t = xs(i)
    xs(i) = xs(j)
    xs(j) = t
  }
  def sort(left: Int, right: Int): Unit = {
    val pivot = xs((left + right) / 2)
    var i     = left
    var j     = right

    while (i <= j) {
      while (xs(i) < pivot) i += 1
      while (xs(j) > pivot) j -= 1
      if (i <= j) {
        swap(i, j)
        i += 1
        j -= 1
      }
    }
    if (left < j) sort(left, j)
    if (j < right) sort(i, right)
  }
  sort(0, xs.length - 1)
  xs
}

写経してみたが、手続き的な実装はいまいち分からん。。

Haskell

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x : xs) =
  let smallerOrEqual = [a | a <- xs, a <= x]
      larger = [a | a <- xs, a > x]
   in quicksort smallerOrEqual ++ [x] ++ quicksort larger

*Main> quicksort [10,8,1,3,4,12,4,1,3,9,8]
[1,1,3,3,4,4,8,8,9,10,12]

なんと分かりやすいことか。。ピボット a より小さいものと大きいものに分けてそれを再帰する。とても分かりやすい。

これをScalaで書いてみる。

def quickSort(list: List[Int]): List[Int] = {
  list match {
    case Nil => Nil
    case ::(x, xs) =>
      val smallerOrEqual = for (a <- xs; if a <= x) yield a
      val larger = for (a <- xs; if a > x) yield a
      quickSort(smallerOrEqual) ++ List(x) ++ quickSort(larger)
  }
}

ロジックがそのまま記述されており、手続き的な実装と比べて明らかに読みやすい。

Haskell 勉強メモ6

再帰

関数型言語では再帰はよく出てくる。

最大値を求める関数を考えてみる。命令的に考えると、現在の最大値を保持する変数を用意し、リストの全ての要素についてループして、変数を更新していく形になる。Scalaで書いてみると

def maximum(list: List[Int]): Int = {
  var max = list.head
  list.foreach { v =>
    if (v > max) max = v
  }
  max
}

となる。再帰的に書く場合最初に基底部(解を明示的に定義しなければならないケース)を定義しなければならない。単一要素のリストに対する最大値は、その唯一の要素と同じ値である。それ以上の場合は、リストの先頭要素と残りのリストの最大値でどちらが大きいかを見る。Scalaで書いてみる。

def maximum(list: List[Int]): Int = {
  def max(a: Int, b: Int): Int = if (a >= b) a else b
  list match {
    case Nil => throw new IllegalArgumentException
    case ::(head, next) if list.size == 1 => head
    case ::(head, next) => max(head, maximum(next))
  }
}

要素が1つのみの表現がわからなかったので、ガードでかいた。

Haskellでは

maximum' :: (Ord a) => [a] -> a
maximum' [] = error "maximun of empty list!"
maximum' [x] = x
maximum' (x : xs) = max x (maximum' xs)

*Main> maximum' [1, 3, 5, 0, 13, 11, 12]
13

replicate

Int と値をとり、その値を指定された数だけ繰り返したリストを返す。

Haskell には元々ある。

*Main> replicate 3 5
[5,5,5]

基底部を考える。0 以下の回数だけ複製することはできないので、この際は空のリストを返す。

replicate' :: Int -> a -> [a]
replicate' n x
  | n <= 0 = []
  | otherwise = x : replicate' (n - 1) x

Scalaで書く場合

def replicate[T](n: Int)(x: T): List[T] =
  n match {
    case k if k <= 0 => Nil
    case k           => x +: replicate(n - 1)(x)
  }

replicate(3)(5) // val res2: List[Int] = List(5, 5, 5)

Scala側はカリー化する必要はあまりないが、Haskell と合わせている。

take

takeは指定されたリストから指定された数を返す。

take' :: Int -> [a] -> [a]
take' n _
  | n <= 0 = []
take' _ [] = []
take' n (x : xs) = x : take' (n -1) xs

*Main> take' 3 [1,2,3,4,5]
[1,2,3]

Scalaでの記述

def take[T](n: Int)(xs: List[T]): List[T] =
  (n, xs) match {
    case (k, _) if k <= 0    => Nil
    case (_, Nil)            => Nil
    case (k, ::(head, next)) => head +: take(k-1)(next)
  }

take(2)(List(1,2,3,4,5)) // val res3: List[Int] = List(1, 2)

Scalaのリストメソッドには、takeはあらかじめ実装されているので実用上はわざわざ作る必要はない。

override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else {
    val h = new ::(head, Nil)
    var t = h
    var rest = tail
    var i = 1
    while ({if (rest.isEmpty) return this; i < n}) {
      i += 1
      val nx = new ::(rest.head, Nil)
      t.next = nx
      t = nx
      rest = rest.tail
    }
    releaseFence()
    h
  }

手続き的な実装にはなっているが。

reverse

関数をリストに取り、逆順のリストを返す

reverse' :: [a] -> [a]
reverse' [] = []
reverse' (x : xs) = reverse' xs ++ [x]

Scala だと

def reverse[T](xs: List[T]): List[T] = xs match {
  case Nil => Nil
  case ::(head, next) => reverse(next) :+ head
}

reverse(List(1,2,3,4,5)) // val res3: List[Int] = List(5, 4, 3, 2, 1)

repeat

受け取った要素から無限リストを作る関数。Haskellは基本遅延評価だから、無限リストでも途中で切るようにとれば問題ない。

*Main> take 3 (repeat 5)
[5,5,5]

再帰的実装は

repeat' :: a -> [a]
repeat' x = x : repeat' x

*Main> take 3 (repeat' 3)
[3,3,3]

Scalaの通常のListは遅延評価ではないから、真似はできないか調べたら LazyList っていうのがあるのを知った。

val lazyList: LazyList[Int] = LazyList.continually(1).take(15)
var count = 0
lazyList.foreach { v => count += v}
println(count) // 15

API ドキュメントに記載されているサンプルが分かりやすい。

val fibs: LazyList[Int] = {
  0 #:: 1 #:: fibs.zip(fibs.tail).map { n =>
    println(s"Adding ${n._1} and ${n._2}")
    n._1 + n._2
  }
}
fibs.take(5).foreach(println)
// 0
// 1
// Adding 0 and 1
// 1
// Adding 1 and 1
// 2
// Adding 1 and 2
// 3

fibs.take(6).foreach(println)
// 0
// 1
// 1
// 2
// 3
// Adding 2 and 3
// 5

アクセスされて初めて評価されていることが分かる。

zip

2つのリストを引数にとり、これらを綴じ合わせて返す。

Prelude> zip [1,2] ['a', 'b', 'c']
[(1,'a'),(2,'b')]

Scala

List(1,2,3).zip(List("a", "b")) // val res9: List[(Int, String)] = List((1,a), (2,b))

実装。どちらか一方が空のリストの時は、空のリストを返す。

どちらの先頭要素もあれば、先頭要素をとったタプルを作り、再帰する

zip' :: [a] -> [b] -> [(a, b)]
zip' _ [] = []
zip' [] _ = []
zip' (x:xs) (y:ys) = (x, y) : zip' xs ys

Scalaで書くと

def zip[A, B](a: List[A])(b: List[B]): List[(A, B)] =
  (a, b) match {
    case (_, Nil)               => Nil
    case (Nil, _)               => Nil
    case (::(x, xs), ::(y, ys)) => (x, y) +: zip(xs)(ys)
  }

zip(List(1, 2, 3))(List("a", "b")) // val res11: List[(Int, String)] = List((1,a), (2,b))

elem

値とリストを受け取り、その値がリストに含まれるか調べる関数

*Main> 1 `elem` [2,3,4]
False

Scalaだと contains にあたるかなと。

List(2,3,4).contains(1)

Haskell 再帰での実装。基底部は空のリストを受け取った時のFalse。リストに要素がある場合は、先頭と値を比較して一致しなければ、残りの要素に対して再帰する。

elem' :: (Eq a) => a -> [a] -> Bool
elem' a [] = False
elem' a (x:xs)
    | a == x = True
    | otherwise = a `elem'` xs

Scalaでの実装

@tailrec
def elem[A](a: A)(list: List[A]): Boolean = (a, list) match {
  case (_, Nil) => false
  case (a, ::(x, _)) if a == x => true
  case (a, ::(_, xs)) => elem(a)(xs)
}

elem(1)(List(2,3,4)) // val res13: Boolean = false
elem(1)(List(2,1,3)) // val res14: Boolean = true