Scalaで無限リスト:Haskellライクに

ScalaにはStreamという無限リストがあるんだけど、微妙に使いづらい、というか分かりづらい。Haskellでいうcycleはどうだ、とかよく忘れるのでメモ。

scala code
  1. def repeat[T](a:T) = Stream.const(a)
  2. def cycle[T](a:Iterable[T]) = Stream.const(a).flatMap(v=>v)
  3. def iterate[T](f:T => T, x:T):Stream[T] = Stream.cons(x, iterate(f, f(x)))
  4. def replicate[T](n:int, elem:T) = Stream.make(n, elem)
  5.  

こんな感じかな。cycleは結構使うから、Streamに標準でありそうな気がするんだけど、ないような。というわけで上のような定義となる。

scala code
  1. repeat(1) take 10 print
  2. // => 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, Stream.empty
  3.  
  4. cycle(1 to 4) take 10 print
  5. // => 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, Stream.empty
  6.  
  7. iterate((x:int)=>x+1, 0) take 10 print
  8. // => 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, Stream.empty
  9.  
  10. replicate(3, 1) take 10 print
  11. // => 1, 1, 1, Stream.empty
  12.  

うんうん。

07.27.08/12am

基礎を終えた人のScalaミニtips

どう書く?orgでScalaを書いてくれる人もでてきたので、超一部の方向けにScalaのことでも書いてみようと思います。言うなれば、基礎を終えた人のScalaミニtips。知っている人は知っている、でもあまり知られていないことを並べていきます。

下に行くほどマニア度あがります、たぶん。FPよりの話が多いかもしれません。はやりのYコンビネータの話とか。たぶん。

ではいってみましょう。

Predefされているものは把握しておきましょう

Scalaにはscala.Predefというオブジェクトがあります。この中で定義されているものは常にインポートされていて使える状態になっているので把握しておきましょう。

例:

scala code
  1. def exit(status: Int): Nothing = {
  2.   java.lang.System.exit(status)
  3.   throw new Throwable()
  4. }
  5.  
  6. def assert(assertion: Boolean) {
  7.   if (!assertion)
  8.     throw new java.lang.AssertionError("assertion failed")
  9. }
  10.  
  11. def print(x: Any) = Console.print(x)
  12. def println() = Console.println()
  13. def println(x: Any) = Console.println(x)
  14.  

C言語ライクなforはどうかくの?

こうです。

scala code
  1. val a = Array(1,2,3,4,5)
  2. var i= -1;while({i += 1; i < a.size;}) {
  3.   println(a(i))
  4. }
  5.  

setterを使おう

setHogeみたいなのは、カッコわるいです。RubyのアクセッサーしかりPythonのpropertyしかり、instance.name = value形式で扱えるのがモダンな言語というものです。

scala code
  1. class Test {
  2.   var _name = "default"
  3.   def name_=(newValue:String) { _name = newValue }
  4.   def name = _name
  5. }
  6.  
  7. val a = new Test
  8. a.name = "new"
  9. println(a.name)
  10.  

単項演算子も定義できます。

できます。

scala code
  1. class Test {
  2.   var _name = "default"
  3.   def name_=(newValue:String) { _name = newValue }
  4.   def name = _name
  5.   def unary_- = "unary:" + name
  6. }
  7.  
  8. val a = new Test
  9. a.name = "new"
  10. println(-a)
  11.  

applyでオブジェクトをメソッドのように呼び出せるよね? hoge(index) = valueはオーバーライドできないの?

できますよ。なんとupdateというすげーふつーの名前のメソッドを定義するんです。

scala code
  1. object dictionary {
  2. val data = Array(null, "A","B","C")
  3.  
  4. def apply(x:String) = x match {
  5.   case "one" => data(1)
  6.   case "two" => data(2)
  7.   case "three" => data(3)
  8. }
  9.  
  10. def update(x:String,y:String) = x match {
  11.   case "one" => data(1) = y
  12.   case "two" => data(2) = y
  13.   case "three" => data(3) = y
  14. }
  15.  
  16. }
  17. dictionary("one") = "X"
  18. dictionary("two") = "Y"
  19. dictionary("three") = "Z"
  20. println(dictionary("one")+","+dictionary("two")+","+dictionary("three"))
  21.  

可変長引数は取れますか。また、リストや配列を展開してメソッドにわたせますか。

もちろん。

scala code
  1. def sumPlus(plus: Int, n: Int*) = plus + sum(n :_*)
  2.  

JAVAのObject型可変長変数をとるメソッドはどう呼びますか?

ちょっとめんどくさいですが、こうです。

scala code
  1. String.format("%d %s", List(1, "hoge").map(_.asInstanceOf[AnyRef]).toArray)
  2.  

インスタンスのメソッドを束縛できますか?そのとき、メソッドがオーバーロードされている場合はどうしますか?

こうします。オーバーロードされている場合は_の後ろに型をつけます。

scala code
  1. val format = (new SimpleDateFormat("dd")).format _:Date => String
  2.  

ここからFPよりです。

カリー化はできますか?

Function.curriedを使います。uncurriedもあります。

scala code
  1. def test(i:int, j:int) = {
  2.   printf("i:{0}, j:{1}", i, j)
  3. }
  4. val f = Function.curried(test _)(1)
  5. f(1)
  6.  

遅延評価は?

lazyを使います。Streamも使いこなせるとハッピーです。

たとえば、無限フィボナッチ数列は以下のように定義します。

scala code
  1. lazy val fib: Stream[Int] = Stream.cons(0,
  2.      Stream.cons(1, fib.zip(fib.tail).map(p => p._1 + p._2)))
  3.  

先生、関数合成がしたいです・・・

Haskellでは短く直感的にかけるからいいですよね。

Scalaでは正直、逆に長ったらしくなりますが、どうしてもというなら・・・

scala code
  1. ({ x:int => x + 1 } andThen { x:int => x * 2 })(3) // => 8
  2. ({ x:int => x + 1 } compose { x:int => x * 2 })(3) // => 7
  3.  

forはモナドに使える構文です。リスト内包表現用ではありません。

リストはモナドですし、HaskellのMaybeに相当するOptionというモナドもあります。map, flatMap, filterメソッドを実装すればforで書けるオブジェクトを定義できます。

Yコンビネータとかいうものの話が(一部で)盛り上がってますが、そういうのってScalaでできるんですか。型があるから複雑だと思うんだけど。

えーと、無名関数で再帰したいんでしょうか?こんな感じでどうでしょう?ふつーはこんなの使いませんけどね。

scala code
  1. def Y[A,B](f:((A=>B),A)=>B,x:A):B = f((x1:A)=>Y(f,x1),x)
  2.  
  3. println(
  4.   Y(
  5.     (f:((int,int)) => int, arg:(int,int)) => arg match { case (x, y) => x match {
  6.       case 0 => y
  7.       case _ => f((x - 1, y + x))
  8.     }} , (10, 0))
  9. )
  10.  

さて、数少ない日本人Scala好きの皆さん。どれくらいご存知でしたでしょうか。まぁ、基礎からちょっとScalaやればこれくらいは皆さんご存知だと思うんですが、やはりScalaはカオスな言語なので、なかなか見つけられない機能も多いんで、最近Scalaやりはじめた人は参考にしてもらえるとうれしいです。

07.27.08/12am

Scalaでパーサコンビネータ

Scala 2.6.0-RC1 でscala.util.parsing.combinatorが標準パッケージになりました。というわけでリファレンスとちょっとしたサンプルくらしかなかったのだけど、とりあえず書いてみた。

どう書く?orgに投稿した、ExcelライクCSVのパーサ。

scala code
  1. import scala.util.parsing.combinator.{Parsers, ImplicitConversions, ~, mkTilde}
  2. import scala.util.parsing.input.CharArrayReader
  3. import Character.isISOControl
  4.  
  5. object CSVParser {
  6.  trait Base
  7.  case class Field(s:String) extends Base {
  8.    override def toString = s
  9.  }
  10.  case class Record(fields: List[Field]) extends Base
  11.  case class File(records :List[Record]) extends Base
  12.  
  13.  def mkString(cs :List[Any]) = cs.mkString("")
  14.  class CSVParser extends Parsers {
  15.    type Elem = Char
  16.    def notMeta(c:Elem) = c!=',' && c!='\n' && c!='"' && !isISOControl(c)
  17.  
  18.    lazy val file = record.*('\n') ^^ File
  19.    lazy val record = (field|quotedField|nullableField).*(',') ^^ Record
  20.    lazy val field = chars.+ ^^ {cs => Field(mkString(cs))}
  21.    lazy val nullableField = chars.* ^^ {cs => Field("")}
  22.    lazy val quotedField = '"' ~ (charsInQuote|quoteInQuote).* ~ '"' ^^ {cs => Field(mkString(cs))}
  23.    lazy val charsInQuote = elem("chars in field", _!='"')
  24.    lazy val quoteInQuote = repN(2, quote) ^^ {cs => '"'}
  25.    lazy val quote = '"' ^^ success
  26.    lazy val chars = elem("chars", notMeta)
  27.  }
  28. }
  29.  
  30. val data = """
  31. "aaa","b
  32. bb","ccc",zzz,"y""Y""y",xxx
  33. """.trim
  34.  
  35. (new CSVParser.CSVParser).file(new
  36. CharArrayReader(data.toCharArray)).map(file => {
  37.  file.records.map({record =>
  38.    val fields = record.fields
  39.    (1 to fields.length).foreach(i => println(i +" => " + fields(i-1)))
  40.  })
  41. })
  42.  

とりあえずこんな感じ。これはダイレクトにParsersクラスを直接継承してるけど、StdTokenParsersってかんじのParserもあるし、StdLexicalってかんじなLexerもあってこれはなかなか。

時間が出来たらもうちょっといじってみよう。よさげな解説してるサイトがあったら是非教えてください。

07.27.08/12am

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages