2016年4月30日土曜日

型安全イコール判定 - ScalazとScalactic

Scalaプログラミングのはまりポイントとして頻出頻度が高く影響も甚大なのは、==メソッドとcontainsメソッドの型チェックだと思います。

この2つのメソッドは(多分Javaとの互換性の問題で)、引数に定義されている型がAnyであるため事実上型チェックが効かない仕様になっています。

具体的には以下のような処理がコンパイルエラーにならず通ってしまうという問題です。

scala> "1" == 1
"1" == 1
res112: Boolean = false

scala> List(1, 2, 3).contains("1")
List(1, 2, 3).contains("1")
res113: Boolean = false

いずれの場合も、常に判定結果はfalseになり、意図した処理でないことは明らかですがScalaコンパイラはエラーとして弾いてくれません。

Scalaは型チェックが厳しいので、きちんとプログラミングしていればケアレスミス的なバグはほとんどでないのですが、逆に出る場合は==メソッドとcontainsメソッドのあたりに集中するというのがボクの実感です。

例題

==メソッドとcontainsメソッドの問題がよく発生する例として、クラスで保持しているプロパティの型がOptionのケースがあります。

以下の例ではcityの型がOption[String]となっています。

case class Person(name: String, city: Option[String])

特によくバグになるパターンとしては最初に:

case class Person(name: String, city: String)

として(Optionなしの)String型で定義していたものを、機能追加などでOption[String]に変更した場合です。このような場合には、コンパイルエラーで影響箇所が検出されることを期待しているわけですが、==メソッドとcontainsメソッドを使っている場所はエラーにならず、ロジック的に常にfalseとなるため、即バグになってしまいます。

準備

説明では以下のクラスとデータを使用します。

case class Person(name: String, city: Option[String])
  val cities = Vector("Yokohama", "Kawasaki", "Kamakura")
  val person = Person("Taro", Some("Yokohama"))
  val persons = Vector(
    Person("Taro", Some("Yokohama")),
    Person("Jiro", Some("Kawasaki")))
ScalazとScalactic

==メソッドとcontainsメソッドの問題に対応するための機能を提供するライブラリとしてScalazとScalacticを使ってみます。

ScalazはMonadicプログラミング向けのライブラリの1機能(型クラスEqual)として==メソッド問題の解を提供しています。

一方Scalacticは、Scalatestのスピンオフ機能で==メソッド問題を中心にオブジェクト操作の便利機能を提供するライブラリです。

ダメロジック

ダメロジックとして==メソッドとcontainsメソッドの例を順にみていきます。

==メソッド

Personのcityプロパティの方はOption[String]なのでStringと比較しても意味がないのですが、以下のように==メソッドでは比較できてしまいます。

person.city == "Yokohama"

コンパイルは通りますが、実行結果は必ずfalseになってしまいます。

containsメソッド

Personの集まりからcitiesに登録された市に所属する人を抽出する処理です。

persons.filter(x => cities.contains(x.city))

本来比較できないPersonのcityプロパティのOption[String]と、citiesに入っているStringを比較していますが、コンパイルエラーになりません。

しかし、containsメソッドの結果は必ずfalseになるため、全体の実行結果は必ず空の集まりが返ってきてしまいます。

Scalazによる解

まずScalazの提供する機能を使ってみます。

準備として以下のimportを行います。

import scalaz._, Scalaz._
==メソッド

Scalazでは、型チェックあり版の==メソッドとして===メソッドを提供しています。

エラー検出

オブジェクトの同定比較として==メソッドの代わりに===メソッドを用いると型チェックしてエラー検出するようになります。

person.city === "Yokohama"
[error] .../src/main/scala/sample/Main.scala:66: type mismatch;
[error]  found   : String("Yokohama")
[error]  required: Option[String]
[error]       person.city === "Yokohama"
[error]                       ^

簡単に使えて効果抜群です。

対応

コンパイルエラーに対する対応は、比較の型を合わせる修正です。

person.city === Some("Yokohama")
containsメソッド

Scalazでは、型チェックあり版のcontainsメソッドとしてelementメソッドを提供しています。

エラー検出

オブジェクトの集まりでの存在確認にcontainsメソッドの代わりにelementメソッドを用いると型チェックしてエラー検出するようになります。

persons.filter(x => cities.element(x.city))
[error] .../src/main/scala/sample/Main.scala:67: type mismatch;
[error]  found   : Option[String]
[error]  required: String
[error]       persons.filter(x => cities.element(x.city))
[error]                                            ^

こちらも簡単に使えて効果抜群です。

対応

コンパイルエラーに対する対応は、比較の型を合わせる修正です。以下の例では、Optionのfoldメソッドを使ってみました。

persons.filter(_.city.fold(false)(cities.element))

Scalacticによる解

Scalacticで同定比較によるコンパイラエラー抽出を行うためには以下のimportを行います。

import scalactic._
import TypeCheckedTripleEquals._

「import TypeCheckedTripleEquals._」を他のものに変えると、同定比較の方式を変更することができます。

==メソッド

ScalacticではScalazと同様に同定比較によるコンパイラエラー抽出用に型チェックあり版の==メソッドとして===メソッドを提供しています。

エラー検出

オブジェクトの同定比較として==メソッドの代わりに===メソッドを用いると型チェックしてエラー検出するようになります。

person.city === "Yokohama"
[error] .../src/main/scala/sample/Main.scala:28: types Option[String] and String do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.CanEqual[Option[String],String]
[error]     person.city === "Yokohama"
[error]                 ^

こちらも簡単に使えて効果抜群です。

対応

コンパイルエラーに対する対応は、比較の型を合わせる修正です。

person.city === Some("Yokohama")
containsメソッド

ScalacticではScalazのelementメソッドに相当する機能はないようなので===メソッドを使って対応してみます。

エラー検出

containsメソッドの代わりにexistsメソッドを使い、同定比較に===メソッドを使ってみました。

persons.filter(x => cities.exists(_ === x.city))
[error] .../src/main/scala/sample/Main.scala:29: types String and Option[String] do not adhere to the type constraint selected for the === and !== operators; the missing implicit parameter is of type org.scalactic.CanEqual[String,Option[String]]
[error]     persons.filter(x => cities.exists(_ === x.city))
[error]                                         ^

containsメソッドを使うより若干ロジックが入りますが、気になるほどではないと思います。

対応

コンパイルエラーに対する対応は、比較の型を合わせる修正です。

persons.filter(x => cities.exists(y => x.city === Some(y)))

ScalazとScalacticの使い分け

型安全な同定比較機能としてScalazとScalacticで同等の使い勝手の機能が提供されていることが確認できました。

Scalazは汎用のMonadicプログラミング向けライブラリなので、Scalazを使用している場合はありがたく===メソッド、elementメソッドを使用するのがよいと思います。

Scalacticは同定比較機能として以下の機能を提供しています。

  • Torerance (値の誤差範囲を考慮した比較)
  • 同定比較方法のカスタマイズ (大文字小文字の区別の有無など)

また以下のユーティリティ機能も提供しています。

  • Or/Every (Validation結果の格納)
  • 事前条件の文字列補完
  • スナップショット用の文字列補完

こういったユーティリティ機能を使う場合にはScalacticが選択肢になります。

===メソッドだけ欲しい場合は、どちらを選んでもよいですが暗黙定義の影響範囲が少なそうなScalaticの方が若干使い勝手がよさそうです。

まとめ

地味な話題ですが結構開発工数に影響する==メソッドとcontainsメソッドの問題と、その解決策についてご紹介しました。

防御的プログラミングを心がけるなら==メソッドとcontainsメソッドは使わないぐらいの心持ちでもよいと思います。

Scalazは===メソッドだけのために採用するのは心理的障壁が高そうですが、その場合はScalacticを選ぶとよいでしょう。

Scalacticは、事前条件の文字列補完、スナップショット用の文字列補完の機能が地味に便利なので、そういう意味でも使い出があると思います。

諸元

  • Java 1.7.0_75
  • Scala 2.11.7
  • Scalaz 7.2.0
  • Scalactic 3.0.0-M15

2016年3月31日木曜日

State的状態機械2016

昨年Scalaにおける状態機械の実装戦略について以下の記事で検討しました。

OOP編はcase classで作った汎用の状態機械オブジェクトをOOP的な枠組みで利用しました。

そして、FP編ではOOP編で作った汎用の状態機械オブジェクトをそのままFP的な枠組みで利用できることを確認しました。

もちろん、これはOOPでもFPでも使用できる汎用の状態機械オブジェクトの作り方を確認するのが目的で、幸いどちらの目的にも使用できるものを作成することができました。

この記事で検討した方針で一年間プログラミングをしてきましたが、改良のポイントが出てきたので、これを反映した2016年版の状態機械オブジェクトを考えてみます。

ParseState

「状態+状態遷移を表現するオブジェクト兼代数的データ型」であるParseStateですが、2016年版では以下の点を変更しています。

  • イベントの入力用メソッドであるeventメソッドとendEventメソッドを統合
  • イベントをParseEventオブジェクトで表現
  • イベントに対する反応を新状態のParseStateと処理結果ParseResultの組を返すようにした

2015年版と機能的には変わらないのですが、Stateモナドの形に合わせることでストリーム処理で使いやすい構造になっています。

sealed trait ParseState {
  def apply(event: ParseEvent): (ParseState, Option[ParseResult])
}

case object InitState extends ParseState {
  def apply(event: ParseEvent): (ParseState, Option[ParseResult]) = event match {
    case CharEvent(',') => (InputState(Vector(""), ""), None)
    case CharEvent('\n') => (InitState, Some(ParseResult.empty))
    case CharEvent(c) => (InputState(Vector.empty, c.toString), None)
    case EndEvent => (InitState, None)
  }
}

case class InputState(
  fields: Vector[String],
  candidate: String
) extends ParseState {
  def apply(event: ParseEvent): (ParseState, Option[ParseResult]) = event match {
    case CharEvent(',') => (InputState(fields :+ candidate, ""), None)
    case CharEvent('\n') => (InitState, Some(RecordParseResult(fields :+ candidate)))
    case CharEvent(c) => (InputState(fields, candidate :+ c), None)
    case EndEvent => (InitState, Some(RecordParseResult(fields :+ candidate)))
  }
}
ParseEvent

2016年版では入力パラメタとして入力イベントを表現するParseEventを使用するようにしました。

2015年版では入力の終了をendEventメソッドでハンドリングしていましたが、2016年版ではEndEventイベントで扱うようになっています。

製品開発で実際に使用した経験で、この構造の方がストリーミング処理に適していることが分かりました。

具体的には、EndEventなどのイベントで状態をクリアな状態に戻すことができるので、scalaz-streamといったReactive-Streams的なパイプラインの部品として使用することが容易になります。

trait ParseEvent
case class CharEvent(c: Char) extends ParseEvent
case object EndEvent extends ParseEvent
ParseResult

計算結果を表現するParseResultは以下になります。

2015年版では、ParseStateが結果を管理していましたが、2016年版では結果をParseResultで外部に出力するようにしました。

ParseEventと同様に製品開発で実際に使用した経験で、Stateモナドとの相性がよいことが分かりました。

sealed trait ParseResult {
  def getRecord: Option[Vector[String]]
}
case object NoneParseResult extends ParseResult {
  def getRecord = None
}
case class RecordParseResult(record: Vector[String]) extends ParseResult {
  def getRecord = Some(record)
}

object ParseResult {
  val empty = RecordParseResult(Vector.empty)
}

Stateモナド

それでは、新版のParseStateをStateモナドで使ってみましょう。

2015年版に比べると結果を取り出す処理が若干複雑になっていますが、それほど大きな違いはありません。

Stateモナドで問題なく使えることが確認できました。

import scalaz._, Scalaz._

object ParserStateMonad {
  def action(event: ParseEvent) = State((s: ParseState) => s.apply(event))

  def parse(events: Seq[Char]): Seq[String] = {
    val xs = events.toStream.map(CharEvent) :+ EndEvent
    val t = xs.traverseS(action)
    val r = t.eval(InitState)
    r.flatten.flatMap(_.getRecord).flatten
  }

  def parseAnnotated(events: Seq[Char]): Seq[String] = {
    val xs: Stream[ParseEvent] = events.toStream.map(CharEvent) :+ EndEvent
    val t: State[ParseState, Stream[Option[ParseResult]]] = xs.traverseS(action)
    val r: Stream[Option[ParseResult]] = t.eval(InitState)
    r.flatten.flatMap(_.getRecord).flatten
  }
}

scalaz-stream版状態機械

次はscalaz-streamのProcessモナドです。

Scala的状態機械/FP編の「scalaz-stream版状態機械」と基本的には同じで、ParseStateの入出力が変更された部分に対応しています。

大きな違いとしては2015年版はParseState自身がストリームを流れてくる構造になっており、ParseStateから計算結果を取り出す処理になっていました。

それに対して、今回はParseStateによる処理結果であるParseResultがストリームを流れてくるので、これを取り出す構造になっています。

いずれにしても、OO版としても使える汎用の状態機械オブジェクトをscalaz-streamのProcessモナドで使えることが確認できました。

import scalaz._, Scalaz._
import scalaz.stream._

object ParserProcessMonadStateMonad {
  def fsm(state: ParseState): Process1[ParseEvent, ParseResult] = {
    Process.receive1 { evt: ParseEvent =>
      ParserStateMonad.action(evt).run(state) match {
        case (s, Some(r)) => Process.emit(r) fby fsm(s)
        case (s, None) => fsm(s)
      }
    }
  }

  def parse(xs: Seq[Char]): Seq[String] = {
    val events = xs.map(CharEvent) :+ EndEvent
    val source: Process0[ParseEvent] = Process.emitAll(events)
    val pipeline: Process0[ParseResult] = source.pipe(fsm(InitState))
    pipeline.map(_.getRecord).pipe(process1.stripNone).toList.last
  }

  import scalaz.concurrent.Task

  def parseTask(xs: Seq[Char]): Task[Seq[String]] = {
    val events = xs.map(CharEvent) :+ EndEvent
    val source: Process0[ParseEvent] = Process.emitAll(events)
    val pipeline: Process[Task, ParseResult] = source.pipe(fsm(InitState)).toSource
    pipeline.map(_.getRecord).pipe(process1.stripNone).runLastOr(Nil)
  }
}

まとめ

状態機械の実装方法について、1年間の実践経験を踏まえて改良版をまとめました。

状態機械の実装方法は色々な選択肢がありますが、OOPかつFPを同時に満たすものとなると選択肢が限られてきます。

OOP化はそれほど難しくないので、焦点はFP化ですが、StateモナドやProcessモナドで使用することを前提にした上で、自然な形の実装方法に落とし込めたと思います。

諸元

  • Java 1.7.0_75
  • Scala 2.11.7

2016年2月29日月曜日

ScalaでXSLT

Scalaで半構造データ的なデータ処理に対してどのようなアプローチをとっていくのかは重要な論点の一つだと思いますが、引き続きXMLも有力な選択肢だと思います。

XMLはXML文書をプログラム内にデータ構造として取り込むだけだとあまり面白みはありませんが、XPath, XSLT, XQueryといったデータ操作用の機能を使用すると応用の範囲がぐっと広がります。

この中でもXPathとXSLTはJavaの基本APIに入っているので、Scalaからもシームレスに使うことができます。

そこでScalaでのXSLT使い方を整理してみました。

準備

処理対象とするXML文書です。

<userlist>
  <user zip="221" city="Yokohama" name="Taro"/>
  <user zip="248" city="Kamakura" name="Hanako"/>
</userlist>

XSLTのスタイルシートとして以下のものを使用します。

上記のXML文書をHTMLの表に整形します。

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
    <html>
      <table border="true">
 <thead>
   <th>Name</th>
   <th>Zip</th>
   <th>City</th>
 </thead>
 <tbody>
          <xsl:apply-templates select="/userlist/user"/>
 </tbody>
      </table>
    </html>
  </xsl:template> 

  <xsl:template match="user">
    <tr>
      <td><xsl:value-of select="@name"/></td>
      <td><xsl:value-of select="@zip"/></td>
      <td><xsl:value-of select="@city"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>

xsltprocコマンドでXML文書の変換を行うと以下のようにXHTML文書が出力されます。

$ xsltproc app.xsl data.xml
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml"><table border="true"><thead><th>Name</th><th>Zip</th><th>City</th></thead><tbody><tr><td>Taro</td><td>221</td><td>Yokohama</td></tr><tr><td>Hanako</td><td>248</td><td>Kamakura</td></tr></tbody></table></html>

このXHTML文書を見やすいように整形すると以下になります。

<html xmlns="http://www.w3.org/1999/xhtml">
  <table border="true">
    <thead>
      <th>Name</th>
      <th>Zip</th>
      <th>City</th>
    </thead>
    <tbody>
      <tr>
        <td>Taro</td>
        <td>221</td>
        <td>Yokohama</td>
      </tr>
      <tr>
        <td>Hanako</td>
        <td>248</td>
        <td>Kamakura</td>
      </tr>
    </tbody>
  </table>
</html>

XSLTプロセッサを使う

スタイルシートとXMLデータの両方をStringで受取り、変換結果をStringで返す処理を関数「StringのスタイルシートでStringのXMLデータを変換」として作成しました。

def StringのスタイルシートでStringのXMLデータを変換(stylesheet: String, data: String): String = {
    val stylesource = new StreamSource(new StringReader(stylesheet))
    val transformer = TransformerFactory.newInstance().newTransformer(stylesource)
    val source = new StreamSource(new StringReader(data))
    val buf = new StringWriter()
    val result = new StreamResult(buf)
    transformer.transform(source, result)
    buf.toString
  }

JavaのXSLTプロセッサであるjavax.xml.transform.Transformerは、スタイルシート、XML文書、変換結果のそれぞれに対して以下の入力の選択肢があります。

  • 文字列
  • DOM
  • SAX

ここでは、スタイルシート、XML文書、変換結果のすべてに文字列を使う組合せを実装しています。

XMLリテラルとDOM

ScalaでXMLを扱う時に少しややこしいのはXMLリテラルの存在です。

XMLリテラルはScalaの文法に組み入れられており、さらにScala的、DSL的なAPIで木構造の操作を簡単に行えるようになっているメリットがある反面、XMLの共通機能であるXPath、XSLTを使用することができない、というデメリットがあります。

この問題への対応方法は、DOMやSAXといったJavaが提供しているパーサーとXMLリテラルの相互変換です。

以下ではこの相互変換で一番楽な方法である文字列を媒介とした変換を採用しました。スタイルシートをXMLリテラルのオブジェクトであるscala.xml.Nodeで受取り、それを文字列表現に変換して「StringのスタイルシートでStringのXMLデータを変換」関数に渡しています。

def XMLリテラルのスタイルシートでStringのXMLデータを変換(stylesheet: scala.xml.Node, data: String): String =
    StringのスタイルシートでStringのXMLデータを変換(stylesheet.toString, data)

文字列表現を媒介にする方法は一般的には十分ですが、大規模データ操作などで性能が求められる場合は適さないかもしれません。

そのようなケースでは、javax.xml.transform.sax.SAXSourceをextendsしたXMLリテラル用のSourceを作成して対処するとよいでしょう。XMLリテラル用のSAXパーサーorg.xml.sax.XMLReaderを作成し、このSAXSourceでラップするような形になります。

使ってみる

スタイルシートをXMLリテラルで、XML文書を文字列で受け取って、変換結果を文字列で受け取る関数「XMLリテラルのスタイルシートでStringのXMLデータを変換」で処理するプログラムは以下になります。

生文字列リテラルでも問題はないですが、やはりXML専用のXMLリテラルでXSLTによる変換処理を記述できると便利です。

def main(args: Array[String]) {
    val stylesheet = <xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
    <html>
      <table border="true">
 <thead>
   <th>Name</th>
   <th>Zip</th>
   <th>City</th>
 </thead>
 <tbody>
          <xsl:apply-templates select="/userlist/user"/>
 </tbody>
      </table>
    </html>
  </xsl:template> 

  <xsl:template match="user">
    <tr>
      <td><xsl:value-of select="@name"/></td>
      <td><xsl:value-of select="@zip"/></td>
      <td><xsl:value-of select="@city"/></td>
    </tr>
  </xsl:template>
</xsl:stylesheet>
    val data = """<userlist>
  <user zip="1234567" city="Yokohama" name="Taro"/>
  <user zip="1234567" city="Kamakura" name="Hanako"/>
</userlist>
"""
    val result = XMLリテラルのスタイルシートでStringのXMLデータを変換(
      stylesheet, data)
    println(result)
  }

プログラム実行の結果、無事以下の出力が得られました。

<?xml version="1.0" encoding="UTF-8"?><html xmlns="http://www.w3.org/1999/xhtml"><table border="true"><thead><th>Name</th><th>Zip</th><th>City</th></thead><tbody><tr><td>Taro</td><td>1234567</td><td>Yokohama</td></tr><tr><td>Hanako</td><td>1234567</td><td>Kamakura</td></tr></tbody></table></html>

諸元

  • Java 1.7.0_75
  • Scala 2.11.7

2016年1月31日日曜日

[SDN] 例外とNothing

try/catchで例外処理を書く場合、以下のように例外に対する共通処理を羅列する形になることがあります。

すべての例外に対して同じ処理を行う場合には、大本のjava.lang.Exception(場合によってはjava.lang.Throwable)でキャッチすればよいですが、以下のように例外毎に少しずつ処理が異なる場合には羅列せざるを得ません。

def doSometing(): String = {
  try {
    doSomesing()
  } catch {
    case e: FileNotFoundException =>
      log("file not found")
      throw e
    case e: InterruptedIOException =>
      log("intterupted io")
      throw e
    case e: IOException =>
      log("io")
      throw e
    case NonFatal(e) =>
      log("non fatal")
      throw e
  }
}

上記のプログラムでは以下の処理が例外の共通処理になります。

log("file not found")
throw e

「例外毎に異なったメッセージをログに出力して、受け取った例外を再送出する処理」ですが、ログに出力するメッセージの文言が例外毎に変わってきます。

共通処理化

「例外毎に異なったメッセージをログに出力して、受け取った例外を再送出する処理」を関数化して再利用するために以下の関数を作ってみました。

この処理は例題なので単純なものになっていますが、製品開発ではもっと細かくて複雑な処理が必要になってくる可能性があります。また、システム共通のエラー処理を変更する場合には、エラー処理のロジックが一箇所に集まっていた方が取り回しが楽ということもあります。

def handleError(message: String, e: Throwable) {
  log(message)
  throw e
}

この関数は以下のように例外処理に組み込みます。

try {
    doSomesing()
  } catch {
    case e: FileNotFoundException =>
      handleError("file not found", e)
    case e: InterruptedIOException =>
      handleError("intterupted io", e)
    case e: IOException =>
      handleError("io", e)
    case NonFatal(e) =>
      handleError("non fatal", e)
  }

一見これでよいように思いますが、実は以下のようなコンパイルエラーが出てしまいます。

...
[error]  found   : Unit
[error]  required: String
[error]         handleError("file not found", e)
[error]                    ^
...
[error]  found   : Unit
[error]  required: String
[error]         handleError("intterupted io", e)
[error]                    ^
...
[error]  found   : Unit
[error]  required: String
[error]         handleError("io", e)
[error]                    ^
...
[error]  found   : Unit
[error]  required: String
[error]         handleError("non fatal", e)
[error]                    ^
[error] four errors found

その理由は、handleError関数の返却値がUnitであるため、例外処理を行っているdoSomething関数の返却値Stringと型が合わないためです。

この問題の対策が今回のテーマです。

Stringを返す

利用者側の関数の返却値とhandleError関数の返却値が合わない問題を解決する方法として、返却する型を明示したhandleError関数を用意する方法があります。

たとえば以下のようなhandleErrorString関数を用意する方法です。

def handleErrorString(message: String, e: Throwable): String = {
  log(message)
  throw e
}

しかし、この方法を採ると必要な型ごとに1つ関数を用意する必要があります。用途によっては考えられる解決策ですが、汎用的に適用できる方法ではありません。

型パラメータ

1つの解としては以下のように型パラメータを使う方法があります。

def handleError[T](message: String, e: Throwable): T = {
  log(message)
  throw e
}

handleError関数の使用元が必要とする型が自動的に型Tに設定されhandleError関数の返却値となります。このためコンパイルエラーになりません。

Nothing

前述の型パラメータを使う方法でもよいのですが、Scalaにはこのような目的に使用できるNothingという型があります。

Nothingはすべての型のサブクラス、という特殊な位置付けの型です。一種のワイルドカードのような型です。

以下のようにhandleError関数の返却値をNothingとすると、doSomething関数がどのような型を返すことになっても、そのまま利用することができます。

def handleError(message: String, e: Throwable): Nothing = {
  log(message)
  throw e
}

この例のように最後に例外を送出する処理を行う関数は、実際には値は返すことはないので、返却値の型をNothingしても問題はなく、利用範囲は大きく広がります。

今回の問題に対しては前述の型パラメータを使う方法でも対処可能ですが、Nothingを使った方がより意図が明確になると思います。

参考

未実装のメソッドを定義するのに便利な「???」メソッドも返却値にこのNothingを使っています。「???」メソッドの定義は以下のようになっています。

def ??? : Nothing = throw new NotImplementedError

諸元

  • Java 1.7.0_75
  • Scala 2.11.7

2015年12月30日水曜日

MindmapModeling 2015

MindmapModelingの最新状況についてのご紹介です。

MindmapModelingはマインドマップを使ってモデリングを行う手法です。マインドマップを使って自由にモデルを書くというのではなく、UMLメタモデルによるモデルの表記方法としてマインドマップを用いるというアプローチです。

wakhok時代(2007年ごろ)に教育用として考案してモデリングの授業で使用していました。雑誌連載したものを書籍としてもまとめました。

MingmapModelingの比較的最近の記事としては2012年のものがあります。

その後、活動の中心がクラウドサービスプラットフォームの開発になったため情報発信は減りましたが、モデルコンパイラSimpleModelerなど他のモデリング技術と同時に開発技術として内部利用してきました。

クラウドアプリケーションとMindmapModeling

前述した通りMindmapModelingは元々教育用に開発したものですが、クラウドアプリケーション開発では実用技術として使えるのではないかと考えています。

モデリング技術全体に対するアプローチは以下で取り上げました。

クラウドサービスプラットフォームとScalaによるDSL指向Object-Functional Programming(OFP)のコンボによってアプリケーション開発におけるアプリケーションロジックのプログラミング量は劇的に減らすことができると考えています。

そうなると開発技術の焦点は以下の2つになります。

  1. UIやその他の体験(リコメンドの精度など)を統合したUX(User eXperience)
  2. ビジネスとのつなぎ込み

その中で(2)ビジネスとのつなぎ込みはまさにモデリング技術によって解決する分野ですが、ここにMindmapModelingがぴったりはまるのではないかと考えています。(1)のUXもUI的なチューニングではない、上記では「その他の体験」としている部分はアプリケーションが提供するフィーチャーそのものなので、機能要件あるいは非機能要件としてモデル化する必要があります。この目的でもMindmapModelingを使用することができます。

ビジネスとのつなぎ込み

MindmapModelingはビジネスとのつなぎ込みのモデルとして使用するため以下の2つの要件を満たすことを指向しています。

  • ビジネスサイドのサービス企画担当者と開発担当者が共有して議論のベースとして使用できるモデル
  • システム開発のオブジェクト(+関数)モデルにシームレスに連携できるモデル

MindmapModelingでは記述方式にマインドマップを使うことと、メタモデルを絞り込むことでこの要件を満たすこと意図しています。

従来的なシステム開発プロセスは以下のようなモデルの連携になります。

  • ビジネス設計-システム要求-分析-設計-実装

MindmapModelingはこの中のビジネス設計の後半、システム要求、分析の前半ぐらいまでをざっくり(教育向けに)カバーすることを目的にしていました。

そして、クラウド時代になるわけですが、ここではクラウドサービスプラットフォーム&Scalaの組合せにより、以下の形に持ち込めるのではないかという仮説を立てています。

  • ビジネス設計-システム要求-実装

この中のビジネス設計の後半とシステム要求をMindmapModelingで行い、そのまま直接Scalaプログラミングにつなげてしまうというのが新しい方程式です。ベースとなるクラウドサービスプラットフォームが固定化されていることとScalaのDSL機能、この2つの技術を組み合わせることで分析と設計のアクティビティを省いてしまう(i.e. プログラマが暗算で行う)ことが可能ではないかというのが仮説の眼目です。実際にApparelCloudの開発はこの方針にそって行っています。

ビジネス設計

ビジネス設計の後半からシステム要求はMindmapModelingで行うとして、次に必要になるのは、一つ上流にあるビジネス設計(の前半)です。

ここでいうビジネス設計はビジネスの意図、ゴール、メカニズムをオブジェクト・モデル化したものを意図しています。(具体的にはEriksson-Penkerをベースにしたメタモデルをイメージしています。)

EverforthではApparelCloudのビジネス領域とプラットフォーム向けのメタモデルを開発中です。(来年中にはご紹介できると思います。)

また、より汎用的な用途のメタモデルとしては匠Methodも選択肢になります。

ビジネス設計の技法としてEverforth方式、匠Method、あるいはその他のメソッドを目的に合わせて選んでも、ビジネスサイドとエンジニアとの情報共有にはMindmapModelingを使用することで、開発側へのインパクトを最小限に抑えることができます。

MindmapModeling 2015

MaindmapModeling 2015年版です。

雛形

雛形の図は以下になります。


MaindmapModeling 2015年半の例としてECサイトをモデリングしてみました。


ざっくりとイメージはつかんでいただけると思います。

改良点

マインドマップはオリジナルから以下の点を改良しています。

  • 設計向けにチューニング
  • クラウド向けのモデル
設計向けにチューニング

wakhok時代(2008年ごろ)に考案したMindmapModelingはモデリングへの導入を意図していたので「登場人物」や「道具」といったメタファを使っていましたが実務で使う上では逆にオブジェクト・モデリングとの連続性が分かりづらいという問題がありました。

この問題に対応するために、枝の名前を「登場人物→Actor」というように英語の正式用語化しました。

また、要求、分析レイヤーのモデルを指向していたので設計時に必要なモデル要素は意図的に省いていました。用途に応じて設計向けのモデルも記述できるようにTraitやPowertypeといった枝を追加しています。

クラウド向けのモデル

MindmapModelingのメタモデルに以下のモデル要素を追加しました。

Dataflow
データフロー。
Summary
サマリーエンティティ。
Document
データ表現/転送用途の不変オブジェクト。

SummaryとDocumentは元々SimpleModelingにはメタモデルとして組み込んでいましたが、分析目的、教育目的にはモデルが複雑になりすぎるのでMindmapModelingには導入していなかったモデル要素です。

Dataflow

クラウドアプリケーションでは、CQRSアーキテクチャに見られる通り、バックエンドでの非同期大規模計算がシステムの基本機能になってきます。

この処理を記述するためのモデルとしてデータフローを用意しました。

MindmapModelingでは、厳密なデータフロー・モデルを記述することは目的としていません。

Summary

クラウドアプリケーションでは、問い合わせに必要なデータ集計等を事前計算しておいて専用のテーブルなどに格納しておくことが重要な処理になります。

ざっくりとは業務システムで使われているバッチ処理と考えてよいと思います。

この目的で使用するテーブルなどのデータストアを表現するためのエンティティとしてSummaryを導入しました。

前述のDataflowで作成した事前計算結果をSummaryに格納するという位置付けになります。

Document

Documentは伝票を記述するモデルです。以下の利用方法を想定しています。

  • データをプログラム内で持ちまわる容れ物
  • XML, JSONの形式で転送可能になっている
  • 代数的データ型

Scalaではplay-jsonなどでJSONとの相互変換を担保したcase classで実装することを想定しています。

このレイヤーのモデルを分析段階でどこまで記述するのかは判断の別れるところですが、MidnmapModelingの方針として設計指向に寄せたので、その一環でメタモデルとして取り入れました。

このため、必要に応じて使用するという温度感のモデルになります。

設計への流れ

通常のモデルはMindmapModelingから直接Scalaプログラミングしても問題ありませんが、ある程度複雑なモデルの場合や新しく取り組む業務分野などの場合はメモ的に設計モデルをつくるのが効率的です。また外部連携が必要な処理では、きっちりとした仕様書が必要になります。

逆にいうとモデリング作業のすべてをMindmapModeling上で完結させることは目的としていません。基本はMindmapModeling上で行い、必要に応じてオブジェクト・モデリングやDOAなどの技法を適材適所で使う形を想定しています。

ロバストネス図

MindmapModelingで作成したモデルを実装に落とし込むための中間モデルとしてはロバストネス図が有力です。

前述のECサイトのモデル向けのロバストネス図の例を以下になります。


このロバストネス図をベースに、必要に応じてコンポーネント図やコミュニケーション図(コラボレーション図)を作成するとよいでしょう。

まとめ

MaindmapModelingの最新状況についてご紹介しました。

2008年ごろからクラウドアプリケーションの開発技法について考えてきましたが商用システムの開発を通して色々と部品建てが揃ってきたので、2016年は具体的な開発方法論として整備していければと思っています。

2015年11月30日月曜日

Scala Tips/Map + Monoid

ちょっと忘れがちですが、ScalazではMapもMonoidとして定義されています。

これが結構便利なのを再認識したのでメモしておきます。

カウンター

サイトごとのPV数をカウントするような用途でMonoidが便利に使えます。

以下のMapを考えます。Mapの値側の型がIntでありMonoidなので、Map全体もMonoidとして機能します。

MapをMonoidとして使用する場合もMapの作成は通常と同じです。

scala> var c = Map[String, Int]("www.abc.com" -> 1)
c: scala.collection.immutable.Map[String,Int] = Map()

サイトごとのカウンタをMapで管理しています。このカウンタを上げる処理は以下のように書くことができます。

scala> c = c.get("www.abc.com") match {
     | case Some(s) => c + ("www.abc.com" -> (s + 1))
     | case None => c + ("www.abc.com" -> 1)
     | }

この処理をMonoidを使って書くと以下になります。Monoidの演算子|+|で、サイトと足したいカウンタ数の組によるMapを足し込みます。

scala> c = c |+| Map("www.abc.com" -> 2)
c: scala.collection.immutable.Map[String,Int] = Map(www.abc.com -> 3)

scala> c = c |+| Map("www.abc.com" -> 5)
c: scala.collection.immutable.Map[String,Int] = Map(www.abc.com -> 8)

別のサイトのMapを足し込むと、Mapの別のエントリとして管理されます。

scala> c = c |+| Map("www.xyz.com" -> 3)
c = c |+| Map("www.xyz.com" -> 3)
c: scala.collection.immutable.Map[String,Int] = Map(www.xyz.com -> 3, www.abc.com -> 8)

リスト

同様の処理はListやVectorでも使用することができます。

以下のMapを考えます。サイトごとのタグの集まりを管理するMapをイメージしています。

scala> var r = Map[String, Vector[String]]("www.abc.com" -> Vector("spring"))
var r = Map(www.abc.com -> Vector(spring))

Monoidの演算子|+|で、サイトと追加したいタグの組によるMapを足し込みます。

scala> r = r |+| Map("www.abc.com" -> Vector("summer"))
r: scala.collection.immutable.Map[String,Vector[String]] = Map(www.abc.com -> Vector(spring, summer))

別のサイトのMapを足し込むと、Mapの別のエントリとして管理されます。

scala> r = r |+| Map("www.xyz.com" -> Vector("autumn"))
r: scala.collection.immutable.Map[String,Vector[String]] = Map(www.xyz.com -> Vector(autumn), www.abc.com -> Vector(spring, summer))

まとめ

値の集まりをMapで管理する処理は、そう頻繁に使うものでもないので毎回必要となる度に実現方式やロジックを考えることになりがちでした。Scalaの場合、mutableなMapの場合はMultiMapを使う方法もありますが、immutableなMapはこの方法は取れないようです。その場合はそれなりのロジックを組む必要が出てきます。

ということでWebで実現方式を調べていた所、以下のページにScalazを使う方法が載っていたというわけです。

いわれてみればこの方法があったか、ということで最近はこのパターンを愛用しています。

Monoid Index

昔調べた記事です。

Monoidに関する2012年ごろのブログのまとめです。

OptionをMonoidで使うと便利という記事。

Scalaプログラミングの肝はMonoidにあり、というわけで結構Monoidは調べていたつもりだったのですが、大きな応用が抜けていました。他にも何か大きな応用があるかもしれません。

諸元

  • Java 1.7.0_75
  • Scala 2.11.6
  • Scalaz 7.1.0

2015年10月30日金曜日

[SDN] 値クラスでケースクラスの型を強化

Scalaでは仕様をできるだけ型化するのがプログラミングのキモとなります。

積極的に型化するメリットはコンパイラでバグの検出をしてもらえる点にあります。Curry-Howard対応によってコンパイルがプログラムが仕様通りに実装されていることの証明となる点がScalaのような関数型言語を使う重要なメリットです。

しかし、ここでいう「仕様」とは型と(型で引数と復帰値を定義した)関数でのみ記述できるので、この「型」化できなかった「仕様」はコンパイラの証明対象から外れてしまいます。(テストプログラムで明示的にテストする必要があります。)

つまり、Scalaプログラミングでは「仕様」をいかに「型」として記述するのかが勝負どころといえます。

この型化を促進する機能の一つが値クラスです。

値クラスは、Longといった基本型の値やStringへの参照値といった値を、新しく定義した型として使用できるようにしたものです。(Value Classes and Universal Traits)

値クラスを使うことで、型を表現する具体的なオブジェクトを生成することなく値に対して適切な型チェックをおこなうことができるようになります。

ケースクラスの設計の際に値クラスを使って型を強化する方法について考えてみます。

基本形

アカウント管理の以下のケースクラスを素材に考えます。

case class Account(
  accountId: String,
  username: String,
  address: String
)
問題点

このケースクラスは実用上は十分ですが、誤った情報を設定されても検知できないという問題があります。

たとえば、以下のようにString型の変数addressは、Accountのaddress属性だけでなくaccountId属性、username属性にも設定することができます。

scala> val address = "Yokohama"
scala> val a = Account(address, address, address)
val a = Account(address, address, address)
a: Account = Account(Yokohama,Yokohama,Yokohama)

これは明らかなバグですがコンパイラではチェックすることができません。

typeで定義

Scalaではtypeキーワードを使って型の別名をつけることができます。

プログラムの可読性が向上する便利な機能です。

type AccountId = String
  type UserName = String
  type Address = String

case class Account(
  accountId: AccountId,
  username: UserName,
  address: Address
)
問題点

ただ別名は型そのものの定義をおこなうわけではないので、問題点は解消されません。

scala> val address = "Yokohama"
scala> val a = Account(address, address, address)
a: Account = Account(Yokohama,Yokohama,Yokohama)

case classを使う

型を明示的に使う場合、クラスを定義します。

今回の場合、以下のようにaccountId, username, addressに対応するクラスを定義します。用途的にはケースクラスが適切なのでケースクラスとして定義しています。

case class AccountId(v: String)
case class UserName(v: String)
case class Address(v: String)

新しく定義した3つのケースクラスAccountId, UserName, Addressを使ったAccountクラスは以下になります。

case class Account(
  accountId: AccountId,
  username: UserName,
  address: Address
)
問題点の解決

まずaddressとして文字列を指定した場合ですが、ケースクラスAccountIdとは型が違うので無事コンパイルエラーとなりました。

scala> val address = "Yokohama"
scala> val a = Account(address, address, address)
<console>:34: error: type mismatch;
 found   : String
 required: AccountId
       val a = Account(address, address, address)
                       ^

ケースクラスAddressを使用した場合も、コンパイルエラーとしてバグを検出できています。

scala> val address = Address("Yokohama")
scala> val a = Account(address, address, address)
<console>:34: error: type mismatch;
 found   : Address
 required: AccountId
       val a = Account(address, address, address)
                       ^

ケースクラスAccountId, UserName, Addressを使うと、正しくAccountオブジェクトの生成を行うことができました。

scala> val accountid = AccountId("A1234")
scala> val username = UserName("Taro")
scala> val address = Address("Yokohama")
scala> val a = Account(accountid, username, address)
a: Account = Account(AccountId(A1234),UserName(Taro),Address(Yokohama))
新たな問題点

基本的にはこの解で十分なのですが、(それぞれ属性を1つしか持たない)ケースクラスAccountId, UserName, Addressを導入したので、Accountオブジェクトを生成する際に生成されるオブジェクト数が増えてしまうという新たな問題がでてきました。

ケースクラスAccountId, UserName, Addressを導入しない前に生成されるオブジェクト数はAccountオブジェクト1つですが、ケースクラスAccountId, UserName, Address導入後はAccountオブジェクトに加えてAccountId, UserName, Addressの各オブジェクトも生成されるので4個のオブジェクトが生成されることになります。実に4倍の量のオブジェクトが生成されるわけです。属性をすべてこの方法で型化すると、1つのオブジェクトを生成毎にこの副作用も含めて1+属性数個のオブジェクトが生成されるようになります。

オブジェクトの生成数が増えると、オブジェクト生成のオーバーヘッドで処理の遅延が発生するだけでなく、ガベージコレクタの負担の増大、メモリ使用量の増大という問題が起きてきます。

小さなアプリケーションの場合はこれでも問題ありません。逆に型化することのメリットの方が大きいので、多少オーバーヘッドがあってもこの手法を採用するほうが得策です。

しかし大規模アプリケーションやフレームワーク的な基盤機能では見過ごすことができない重大な問題となります。

この問題への対応策として使用できるのが値クラスです。

値クラスを使う

属性を一つだけ持つクラスがAnyValをextendsすると値クラスとして定義されたことになります。

通常のクラスでもケースクラスでも使用できます。

ここでは3つのケースクラスを値クラスとして定義しました。

case class AccountId(v: String) extends AnyVal
case class UserName(v: String) extends AnyVal
case class Address(v: String) extends AnyVal

この3つのケースクラスを使ったケースクラスAccountは以下になります。特別な考慮は不要です。

case class Account(
  accountId: AccountId,
  username: UserName,
  address: Address
)

値クラスは通常のクラスに比べるとできることの制約もありますが、今回の用途では普通のクラスと同じように使用することができます。

問題解決の確認

それでは値クラスを使った場合でも、問題点が解決しているかを確認しましょう。

まずaddressとして文字列を指定した場合ですが、ケースクラスAccountIdとは型が違うので無事コンパイルエラーとなりました。

scala> val address = "Yokohama"
scala> val a = Account(address, address, address)
<console>:34: error: type mismatch;
 found   : String
 required: AccountId
       val a = Account(address, address, address)
                       ^

ケースクラスAddressを使用した場合も、コンパイルエラーとしてバグを検出できています。

scala> val address = Address("Yokohama")
scala> val a = Account(address, address, address)
<console>:34: error: type mismatch;
 found   : Address
 required: AccountId
       val a = Account(address, address, address)
                       ^

ケースクラスAccountId, UserName, Addressを使うと、正しくAccountオブジェクトの生成を行うことができました。

scala> val accountid = AccountId("A1234")
scala> val username = UserName("Taro")
scala> val address = Address("Yokohama")
scala> val a = Account(accountid, username, address)
a: Account = Account(AccountId(A1234),UserName(Taro),Address(Yokohama))

まとめ

ケースクラスの設計時に値クラスを使って型を強化する方法について考えてみました。

ケースクラスの属性を値クラス化したケースクラスを使うだけなので若干コーディングの手間はかかるものの簡単に実現できることが分かりました。

値クラスを使用することで、型によるエラーチェックを強化を行いつつ、オブジェクトの生成回数、メモリの使用量の増大を回避できることが期待できます。

Scala Design Note