Scala日記

Scalaの備忘録。ときどき研究の話。

Argmax: Seqが関数であることを利用してmax値のインデックスを簡潔に取る

以前、ScalaSeq が関数であることを書きましたが、

ym.hatenadiary.jp

これを利用して、最大値のインデックスを取得する関数を簡潔に実装することが出来ます。

val seq = scala.util.Random.shuffle(1 to 10)
seq.indices.maxBy(seq)

美しいですね。

maxBy高階関数で、数式記号で言うところの argmax に相当します。ここでは、seq のインデックスを seq(という関数)で seq の中の実際の値に変換して、その値が最大になるようなインデックスを返しています。

Seq の拡張メソッドとして実装したい場合は以下のようにすればOK。

argmax in Scala

None を含むオブジェクトを Java の deep clone ライブラリで clone すると複数の None オブジェクトができる。この None は case 文で match しない。

完全にハマったので覚書。 ディープクローンを手軽にやりたくて、このライブラリを使ったはいいのだけど、

github.com

突然 case 文で Nonecase None => にマッチしなくなった。 そんなことがありうるのかと思って調べてみたら、このあたりで議論されていた。おおよそ同じ現象と思われる。

「Two scala.None$ references do not match」 Google グループ

手軽にやりたいなら、多少遅いが、シリアライズ → デシリアライズが無難。

基本コンストラクタの引数は他のメソッド内で使わない場合 private field として保持されない

入門書で明示的に教えてくれないシリーズその2。

class A(s:String){
    def i = s.toInt
}

class B(s:String)

class C(s:String){
    val i = s.toInt
}

上のようなコードにおいて、Asが private field として保持されるのはみんな知っているが、BCsが private field として保持されるのかどうかを明示的に記述している文献はなかなか見当たらない。 コップ本の基本コンストラクタの項目には、基本コンストラクタの引数は「必要なら、渡された値でフィールドを初期化し」や「クラス本体で使われないフィールドは保持『しなくてよい』」などと書かれているが、「しなくてよい」という書き方は定義としてはなんとも曖昧で実際の動作が気になる。

本当のところどうなっているのかいろいろ調べたが、文献からは確信が持てずじまいだったので、以前書いた

ym.hatenadiary.jp

で自分で調べてみた。やり方は

scala -Xprint:constructors

として、上のコードを張り付け、コンパイル結果を見る。すると、表題の結論であることが分かる。 ちなみに、同じ研究室の方に調べてもらったところによると、Martin Odasky 曰く、

"(メソッドで使われている場合は private field になり、使われていない場合は基本コンストラクタの処理が終わった時点で捨てられるという推測は) That's all true, but it should be treated as an implementation technique. That's why the spec is silent about it. "

とのこと。つまり、この仕組みは特にScalaの仕様に含まれているわけではないということ。 どのようにプログラミングを行うかという作法に関わる結構重要な決め事のようにも思えるのだが、何を仕様に含めるべきかという理念の違いなのだろうか。

参考:

  1. Scala Reference and Primary Constructor Parameters | The Scala Programming Language

Gnu Parallel の良い入門用資料

www.slideshare.net

Option型のコレクションから Some(x) の値 x だけを取り出すには flatten

val opts: Seq[Option[Int]] = Seq(Some(2), None, Some(3))

のようなものから値のあるものだけを取り出して、

List(2, 3)

を作りたいときに、Scalaを覚えたての頃はやり方がよく分からなくて、最初にやっていたのが

opts.filter(_ != None).map(_.get)

だった。その後、Scalaがだいぶ書けるようになった頃に

for(Some(x) <- opts) yield x

やら、

opts.collect{ case Some(x) => x }

となり、最終的にたどり着いたのが

opts.flatten

である。標準ライブラリを理解してくると自然に行き着くことではあるのだが、 それにしても、このようなことは何故か誰も教えてくれない。初学者の頃にこれを書こうとする度に苦労したので、これからScalaを学ぶ人向けに。

=> Unit と () => Unit と Unit => Unit の違い

先日、op: Unit => Unit と書かざるを得ない状況に直面した。それで、この関数を op()として呼び出したときに「多分あなたが意図していることと違うことしてますよ」みたいなwarningが出て気づいたこと。ScalaのUnitは値がないことを表す型なわけだが、「実際に値がない」のではなく、「値がないことを仮想的に表す値()」を持つ型であるということ。

具体的なwarningメッセージはこちら。

Warning:(140, 9) Adaptation of argument list by inserting () has been deprecated: this is unlikely to be what you want.
        signature: Function1.apply(v1: T1): R
  given arguments: <none>
 after adaptation: Function1((): Unit)
      op()
        ^

したがって、Unit => Unit は実際には一つの引数を取って一つの値を返す関数であり、引数を取らない () => Unit とは別物である。そのため、op() のように引数が空っぽの呼び出しでは整合性が取れない。

=> Unit() => Unit については理解していたものの、() => UnitUnit => Unit の違いについてきちんと理解していなかった。何もない、という意味で ()Unitは同じものだと勘違いしており、落とし穴にハマってしまった。しかも、なんというか、引数リストの( )括弧と、値としての()と、型指定の特殊表記としての() =>の意味が重なりあって不思議な読みを引き起こしていて、話をややこしくしている。名前や記号に関する整合性を重視するScalaっぽくない仕様という感じがするが、仕方ないのかな…。

以下の解説がよくわかります。 () => Unit はFunction0が定義されるのに対して、 Unit => Unit はFunction1となるため、Unit型の値(つまり())を引数として取る必要がある。op: Unit => Unit に対してはop(())op(Unit)とすると警告が解消される。

stackoverflow.com

Unitについてはこちら。

www.scala-lang.org

Scala で 言語処理100本ノック 2015 を解く

東北大学 情報科学研究科 情報伝達学講座(乾・岡崎研究室)で作成された自然言語処理入門者のための教材「言語処理100本ノック 2015」というのがあるんですが、これを Scala で解いてみました。

github.com

だいぶん前に書いたもので、問題が公開された直後にソースコードを出すと真の学習者への効果を下げると思い、控えていたものですが、

ただ、4月からやっている研究室などではさすがに終わっているだろうし、そもそもScalaを書こうという人は初学者ではないだろうというこで、初学者以外の学習効率を上げるための参考資料として、また、言語処理とScalaの裾野を広げるために公開しておきます。

主なターゲット層は

  • Python で一周した。Scalaではどう書くのか知りたい」
  • 自然言語処理については大体分かっている。Scalaを学びたい」
  • コップ本を読んでいる(あるいはもう読んだ)。実践的なコードを見ながら学習したい」

などの人です。言語処理100本ノック 2015 が初めての人は、まずは自分の得意な言語で一度解いてみることをおすすめします。特にこれから自然言語処理を学びたいという人にとっては、試行錯誤の過程で色々なことが身につく大変良い教材になっているので、自分でやってみることに大きな意義があると思います。

コードには「Scalaではこう書くだろう」を密に詰め込んでいますが、解説はありません。「Scalaをこれから学ぶ」という人などにも、コップ本を片手に読むと文法と実践的なコードが結びついて理解が深まるのではないかな、と想像しています。