java.nio.ByteBuffer
ByteBuffer は便利
DataInputStream の readFully みたいに指定サイズを
Byte バッファに読み込んで ByteBuffer にして返すメソッド。
この時のデータはリトルエンディアン
import java.nio.{ByteBuffer,ByteOrder} ..snip.. def read[S <: { def read(b: Array[Byte], n: Int, len: Int): Int }](in: S) (size: Int): ByteBuffer = { val b = new Array[Byte](size) val bbuf = ByteBuffer.wrap(b) bbuf.order(ByteOrder.LITTLE_ENDIAN) var num = 0 while (num < size) { val cnt = in.read(b, num, size - num) if (cnt < 0) throw new EOFException() num += cnt } bbuf } ..snip..
読みだしたデータを ByteBuffer にして扱うと
Int, Short, Double, 文字列 等の各種の型でデータを取り出して処理する際に便利。
..snip.. val bbuf = read[InputStream](in)(32) println(" Int data : %d".format(bbuf.getInt(0))) val sdat: Int = bbuf.getShort(4) println(" Short data: %d".format(sdat)) println(" Double data : %.10f".format(bbuf.getDouble(16))) ..snip..
ファイル保存した文字列を表示するときのエンコーディング指定
いまさらだがファイル保存した文字列のエンコーディング指定でハマった。
Shift_JIS でバイナリファイルに保存した文字列を読みだして
ローカルのエンコーディング(この時は EUC-JP)で表示する時のやり方
def getStr(bb: ByteBuffer, pos: Int, size: Int): Pair[String,String] = { val enc = System.getProperty("file.encoding") val buf = new Array[Byte](size) for (i <- 0 until size) buf(i) = bb.get(pos + i) val hex = for (i <- 0 until size) yield { "%02X".format(buf(i)) } (hex.mkString, new String(new String(buf, "SJIS").getBytes(enc), enc)) }
読みだしたデータを ByteBuffer で渡して表示エンコーディング文字列と
その文字列の16進数ダンプを返すメソッド。
データの ByteBuffer とその読み出し位置(pos)、読み出しサイズ(size)を
引数に指定して、読みだした文字列(表示エンコーディング)とその文字列
の16進数ダンプの文字列の Pair を返す。
..snip.. val (hex, str) = getStr(bbuf, 32, 16) println(" str: %s".format(str)) println(" hex: %s".format(hex) ..snip..
夏休みが終わり(ByteBufferと文字列エンコーディング)
今日で8月も終り。
はやかった今年の8月も。
あっという間でしたが、夏休みは3日間いただきました。
でも宿題が残っている..
[Scala] java のアレは scala でどう書くの
基本的なことでも慣れないうちは、どう書けばよいかと、ググる日々..
scala でプログラミングし出して慣れない scala で java のアレはどう書くのか?
と簡単な処理でも勝手が違うので少し戸惑った事。
byte バッファ
java だと
byte[] buffer = new byte[size];
scala で 配列は Array[T] になるので byte 配列は
val buffer = new Array[Byte](size)
DataInputStream の readFully メソッドに java と同じ byteバッファとして指定可能。
import java.io.{File,FileInputStream,DataInputStream} ..snip.. val file = new File(filename) val in = new DataInputStream(new FileInputStream(file)) val size = file.length.toInt val buffer = new Array[Byte](size) using[DataInputStream, Unit) { in => in.readFully(buffer) } ..snip..
break
scala 2.8 から使用可能。ライブラリで対応している。
breakable { .. } で囲ってそのブロック内のループ処理から break
してループを抜ける。
import scala.util.control.Breaks.{break, breakable} ..snip.. breakable { while (true) { val ok = .. // 何かの処理 if (ok) break } } ..snip..
break メソッドで、例外を投げて、breakable メソッドで
例外をキャッチして処理の流れを制御している。
正常処理の流れを例外で制御しているのが、普段、java のコードレビューで
やってはダメと指摘している手前、ちょっと.. 微妙かも
scala/util/control/Breaks.scala の中身(抜粋)
package scala.util.control ..snip.. // 省略 class Breaks { private val breakException = new BreakControl /** A block from which one can exit with a `break''. */ def breakable(op: => Unit) { try { op } catch { case ex: BreakControl => if (ex ne breakException) throw ex } } ..snip.. // 省略 /* Break from dynamically closest enclosing breakable block * @note this might be different than the statically closest enclosing * block! */ def break() { throw breakException } } object Breaks extends Breaks private class BreakControl extends ControlThrowable
構造的部分型(Structural Subtyping)
型の階層構造とは関係なく特定のメソッドを定義して、このメソッドを持つ型として定義できる。
Ruby や python のダックタイピングと同じようなもの
戻り値の型が Unit で引数無しの close メソッドを持つ型を構造的部分型として指定
def using[S <: { def close(): Unit }, U](s: S)(f: S => U): U = { try f(s) finally s.close }
scala では type の抽象型定義 で C の typedef と似たような抽象型を定義することができる
type Hoge = Foobar
抽象型に構造的部分型を定義することも可能
type Closeable2 = { def close(): Unit } def using[S <: Closeable2, U](s: S)(f: S => U): U = { try f(s) finally s.close }
長さによって、短い場合は直接指定して長くなる場合は抽象型に定義するとよさそう。
簡単なインターフェースは構造的部分型を使う。
上限境界、下限境界
上限境界、下限境界を指定して型パラメータまたは抽象型の型に制限をつけることができる。
上限境界(upper bounds)
[T :< Parent]
- T は、型 Parent のサブ型(子の型)を参照
- 型パラメータまたは抽象型の型のスーパー型(親の型: Parent)から許可
- サブ型への境界は Nothing まで。つまり型階層の一番下
trait Hoge { val item: String val t: String def getId: String = item + t } class Foo(val item: String) extends Hoge { val t = "foo" } class Bar(val item: String) extends Hoge { val t = "bar" } object Foo { def checkType[T <: Hoge](a: T, b: T) = a.t == b.t def checkItem[T <: Hoge](a: T, b: T) = a.item == b.item def check[T <: Hoge](a: T, b: T) = a.getId == b.getId def main(argv: Array[String]) { val foo1 = new Foo("2012") val foo2 = new Foo("2011") val bar = new Bar("2012") println(checkType(foo1, foo2)) println(checkType(foo1, bar)) println(checkItem(foo1, foo2)) println(checkItem(foo1, bar)) println(check(foo1, foo2)) println(check(foo1, bar)) } }
結果
$ scala Foo
true
false
false
true
false
false
下限境界(lower bounds)
[T >: Sub]
- T は、型 Sub のスーパー型(親の型)を参照
- 型パラメータまたは抽象型の型はサブ型(子の型: Sub)までを許可
- スーパー型への境界は Any まで。つまり型階層の一番上
class Foobar[+T](elem: T, elems: Foobar[T]) { def add[P >: T](item: P): Foobar[P] = new Foobar(item, this); def getList[P >: T](tail: List[P]): List[P] = { elems match { case null => elem :: tail case _ => elems.getList(elem :: tail) } } } object Foobar { def main(argv: Array[String]) { val foobar = new Foobar[AnyVal](0, null) val foobar2 = foobar.add(1).add(2).add(3).add(4); foobar2.getList(Nil).foreach(println) } }
結果
$ scala Foobar
0
1
2
3
4
型パラメータの変位指定(variance)
型パラメータで型の横に共変(+), 反変(-)の変位アノテーションを付けて変位指定ができる。
変位指定アノテーションを付けない場合は非変(nonvariant)になる。
どのパラメータ型を渡せるかの3種類の規則を指定。
共変(covariant)
scala> class Foo[+T](val a: T) defined class Foo scala> def bar(b: Foo[AnyVal]) = println(b.a) bar: (b: Foo[AnyVal])Unit scala> bar(new Foo[AnyVal](1)) 1 scala> bar(new Foo[Int](1)) 1 scala> bar(new Foo[Any](1)) <console>:10: error: type mismatch; found : Foo[Any] required: Foo[AnyVal] bar(new Foo[Any](1)) ^
- T型とTのサブ型は指定できるが、T のスーパー型は指定できない。
- ScalaのListの定義は共変。List[Int] は Lint[Any] の引数を受け取るメソッドに指定できる。
- イミュータブルなコレクションの型パラメータは共変が便利。
sealed abstract class List[+A] extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqOptimized[A, List[A]] { ..snip..
非変(nonvariant)
scala> class Foo2[T](val a: T) defined class Foo2 scala> def bar2(b: Foo2[AnyVal]) = println(b.a) bar: (b: Foo2[AnyVal])Unit scala> bar2(new Foo2[AnyVal](1)) 1 scala> bar2(new Foo2[Int](1)) <console>:10: error: type mismatch; found : Foo2[Int] required: Foo2[AnyVal] Note: Int <: AnyVal, but class Foo2 is invariant in type T. You may wish to define T as +T instead. (SLS 4.5) bar(new Foo2[Int](1)) ^ scala> bar2(new Foo2[Any](1)) <console>:10: error: type mismatch; found : Foo2[Any] required: Foo2[AnyVal] Note: Any >: AnyVal, but class Foo2 is invariant in type T. You may wish to define T as -T instead. (SLS 4.5) bar2(new Foo2[Any](1)) ^
Scala の ArrayBuffer の定義は非変。
ミュータブルコレクションの型パラメータを非変にしておくと同じ型しかパラメータ型として指定できない制約をかけることができる。
class ArrayBuffer[A](override protected val initialSize: Int) extends Buffer[A] ..snip..
javaでは配列が共変でコレクションは非変。
反変(contravariant)
scala> class Foo3[-T] defined class Foo3 scala> class T1 defined class T1 scala> class T2 extends T1 defined class T2 scala> class T3 extends T2 defined class T3 scala> def bar3(a: Foo3[T2]) = println(a) bar3: (a: Foo3[T2])Unit scala> bar3(new Foo3[T1]) $line1.$read$$iw$$iw$Foo3@1ad9b0f scala> bar3(new Foo3[T2]) $line1.$read$$iw$$iw$Foo3@2445d7 scala> bar3(new Foo3[T3]) <console>:13: error: type mismatch; found : Foo3[T3] required: Foo3[T2] bar3(new Foo3[T3]) ^
関数(変換処理)の型の入力は反変、出力は共変 trait Funtcion1[-T1, +R] extends AnyRef
暗黙の型変換 (implicit conversion)
型変換用の関数の定義に implicit つけると型変換が必要な場面で自動的に型変換関数が実行される。
例えば forへ指定する Int to Int で暗黙の型変換が使用されている。
1 to 3 は RichInt の to メソッドから Range(1,2,3) が返される。
1 の Int は、暗黙の型変換で Int から RichInt に変換されて to メソッドが呼び出される。
scala> val foo = 1 to 3 foo: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)
to メソッド はscala.runtime.RichInt で定義されている。
final class RichInt(val self: Int) extends ScalaNumberProxy[Int] with RangedProxy[Int] { ..snip.. def to(end: Int): Range.Inclusive = Range.inclusive(self, end) ..snip.. }
Int から RichInt への暗黙の型変換関数 intWrapper は、scala.Predef へ extends している scala.LowPriorityImplicits で定義されている。
Int に定義がないメソッドが呼ばれた場合は RichInt で定義しているメソッドを探して見つかった時に RichInt へ変換して該当するメソッドが呼ばれる。
object Predef extends LowPriorityImplicits { ..snip.. }
LowPriorityImplicits には Int 以外にも Byte や Short 等から Rich.. 型への暗黙の型変換関数が用意されている。
class LowPriorityImplicits { ..snip.. implicit def byteWrapper(x: Byte) = new runtime.RichByte(x) implicit def shortWrapper(x: Short) = new runtime.RichShort(x) implicit def intWrapper(x: Int) = new runtime.RichInt(x) implicit def charWrapper(c: Char) = new runtime.RichChar(c) implicit def longWrapper(x: Long) = new runtime.RichLong(x) implicit def floatWrapper(x: Float) = new runtime.RichFloat(x) implicit def doubleWrapper(x: Double) = new runtime.RichDouble(x) implicit def booleanWrapper(x: Boolean) = new runtime.RichBoolean(x) ..snip.. }
同じ種類の型変換の暗黙の関数定義があると暗黙の型変換を自動的に行う際にエラーになる。
scala> implicit def intWrap(a: Int) = new runtime.RichInt(a) intWrap: (a: Int)scala.runtime.RichInt scala> 1 to 5 <console>:9: error: type mismatch; found : Int(1) required: ?{val to(x$1: ?>: Int(5) <: Any): ?} Note that implicit conversions are not applicable because they are ambiguous: both method intWrapper in class LowPriorityImplicits of type (x: Int)scala.runt ime.RichInt and method intWrap in object $iw of type (a: Int)scala.runtime.RichInt are possible conversion functions from Int(1) to ?{val to(x$1: ?>: Int(5) <: An y): ?} 1 to 5 ^
まとめ
- 暗黙の型変換用の関数は「implicit」をつけて定義する。
- 使われる変換関数は自動的に最適な型が選択され変換される。
- 同じ種類の型変換関数が複数ある場合はエラーになる。
- 型変換対照を探すのは同じスコープ内に限定される。
- 暗黙の型変換を使うとそのクラスに無いメソッドを呼び出せるようになって便利な反面、乱用するとわけがわからなくなりそう。
*1:どこでどう型変換されているのかがわからなくなるため