kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Scala初学者はvarを使っても良い

TwitterScala関係のつぶやきをみていると、どうも、特に初学者の方に、

varを使ってはいけないので、Scalaは難しい…

という意見が散見されるようです。

しかし、Scalaを学び始めのときにいきなりvarを断つ必要はなく安心してvarを使ってもいいと思います (そもそもvarが全く不要なら言語仕様にあるわけないですし)。慣れれば、varを明らかに使わなくて 良い箇所はわかってくるので、そこからval/varを使うのを意識しても遅くないと思います。

もちろん、いつもでも全部var使ってるとどうかという議論はあるのですが、それはScalaに慣れてから 考えればよい話です。Scalaを学ぶのにvarを使わないという縛りがきついのなら、無理しなくていいよ というのが言いたいことです。

Elixirを学んでみる(1) - parser combinator

最近、Elixirがなんとなく盛り上がってきている気がするので、この機会に入門してみることにした。お題は相変わらずparser combinator。解説はめんどうくさいのでコードだけ貼り付けておく。

わかったこと。

  • Elixirの文法は結構くせがある。文法はRubyに影響を受けたということだけど、Rubyの文法とはまた別の癖がある
  • Elixirは動的型付き言語だが、関数が定義されているか、引数の数があっているかなどがある程度静的にチェックされるので、凡ミスは意外にコンパイラが見つけてくれる。
  • f.(x) という文法はちょっと気持ち悪い。
  • defで定義したものをオブジェクト化するときに、 &f/1のように引数の数を明示しなければいけないのは、Erlang譲りっぽいけど、ちょっと面倒くさい
  • パターンマッチは普通に使いやすい
  • String.sliceの第二引数で与える範囲がJavaとかのsubstringと違ってちょっと迷った
  • mixで簡単にプロジェクトの雛形とテストの雛形を生成できて便利
  • 関数(第一級でないものの方)を呼び出すときに、fのように書くだけで呼び出せるのはRubyっぽい
  • Ruby以上になんでもかんでもendで閉じる傾向がある

文法は癖があるが、おおむねまっとうな言語だなあというのは初見の印象。

gist.github.com

Kotlinの謎(解明編)

今日書いた

kmizu.hatenablog.com

についてですが、原因がわかりました。まずは以下のコードを見てください

Welcome to Kotlin version 1.0.0 (JRE 1.8.0_91-b14)
Type :help for help, :quit for quit
>>> val a: () -> Int = { 2 }
>>> val b: () -> Unit = { 2 }
>>> println(a())
2
>>> println(b())
kotlin.Unit

変数abの右辺は共に{ 2 }になっており、これは0引数の無名関数を表しています。一方、aの型は引数なしでIntを返す関数、bの型は引数なしでUnitを返す関数となっており、型に互換性がないにも関わらず、代入することができています。

このことから、無名関数の式の型は、期待される型によって(も)決定されるのだろうということがわかります。具体的には、2行目の{ 2 }の型は期待される型が() -> Unitなので、{ 2 }自体が() -> Unit型になり、そこから、

{ -> 2; Unit }

のようにUnitが補われているのだろうと思います。

F.kt:3:25: warning: the expression is unused
  val b: () -> Unit = { 2 }
                        ^

という警告が出る辺りもそれを裏付けていると言えそうです。

Kotlinの謎

Kotlinという言語の型システムは凄くおおざっぱに言ってしまうと、Scalaと非常によく似ています。

  • TopがAny
  • BottomがNothing
  • Byte, Short, Int, Float, ....といった、プリミティブ型相当の型が継承階層に組み込まれていて、型名まで同じ
  • Generics
  • Declaration-site variance
  • Use-site variance

違う部分、たとえばnullabilityを型システムレベルでサポートしてるとかはもちろんあるのですが、AnyとNothingという名前まで真似してる辺り、非常にScalaの影響が大きいのは間違いありません。

一方で、Kotlinがあえて採用しなかったものの一つに、暗黙の型変換があります。たとえば、Scalaでは、任意の型はUnitに暗黙に型変換できます。そのため、たとえば

val x: Unit = 1

というコードはコンパイル可能です。一方で、Kotlinは

val x: Unit = 1

というコードはコンパイルを通りません。これはまあ設計判断としてはどっちもありだと思います。

さて、それはいいのですが、一つ妙な点があります。次のプログラムを見てください:

gist.github.com

runは引数として渡された0引数無名関数を呼び出し、その最後の評価値を返すメソッドです。ここで、run<Unit> { 1 + 1 }とすることで、1 + 1の結果を強制的にUnitに変換しようとしています。普通に考えると、IntからUnit への暗黙の型変換が駄目なのだからコンパイルも通らなそうなのですが、何故かコンパイルを通ってしまいます。

これはKotlinが暗黙の型変換をサポートしていないという事実と反しているようにも思える。あるいは、実装のバグなのかもしれません。というわけで、Kotlinに詳しい方、何故これがコンパイルを通るか(どういうコードが生成されるかはわかっているので、仕様的に何故通るべきなのか)教えていただけないでしょうか(教えてクン)

Macro PEG 0.0.10 リリース

かなり久しぶりのリリースです。主な変更点は、

Conflict between an expression parenthesis syntax and call syntax · Issue #16 · kmizu/macro_peg · GitHub

を修正した点でしょうか。これで、

STRING = STRING_MACRO("\"") / STRING_MACRO("'");
STRING_MACRO(Q) = Q (!(Q / "\\") . / "\\" .)* Q;

のようなPEGにおいて、Q (!(Q / "\\") が正しく Q(!(Q / "\\") の連接として解釈されます(今までは Qの呼び出しと解釈されていた)。修正方法としては、マクロ名につづいてスペースが続かずに引数の括弧が続く場合はマクロ呼出し、スペースが挟まれている場合は連接として解釈するようにしました。当初、グルーピングは[]という別の記号を使おうと思いましたが、記述として気持ち悪いので、現状のシンタックスをあまり変えないようにしました。

Scalaの学習コストを下げるための心得

追記Twitterで、「それって、言語マニアにしかできない技のような気が」という指摘を受けました。自分としては一般的に適用可能な話だと思っていますが、あるいは自分の感性が著しくずれているのかもしれません。その辺承知の上でお読みください。

Scalaは習得が難しい言語だ、とよく言われます。また、実際問題として、Scalaの言語仕様の全体はそれなりに複雑でもあります。しかし、それはたとえばJavaでも言語仕様の全体像を把握するのは難しい話であり、Scalaに限った話ではありません。にも関わらず、Scalaの習得が難しいとよく言われるのはプログラミング言語の学習モデルが誤っているからではないかと最近思うようになりました。そこで、Scala(や他の言語も含めて)のコストを下げるために必要な心得についてちょっと書いてみます。

これは、Scala関数型プログラミング言語である、という主張と相反するように見えますが、オブジェクト指向関数型プログラミングは特に対立するものではありません。一方で、Scalaの核となる部分は、従来のオブジェクト指向言語(たとえばJava)の発展系であり、Scheme/ML/Haskellといったラムダ計算をベースとする言語とは明らかに異なります。Scalaは核となるオブジェクト指向言語関数型プログラミング言語から輸入した機能を取り込んだ言語と見るべきです。ですから、Scalaを使う際にいきなりパラダイム・シフトする必要はありません。必要になったときに理解すればいいのです。

  • 文法の一般形と省略形を区別する

たとえば、Web上にあるScalaのメソッドの文法解説として、

def メソッド名(引数: 引数の型, ...): 返り値の型 = { //返り値の型は省略可能
  return hoge // return は省略可能
}  // 単一行の場合は{}を省略可能

のようなものを見ますが、これは誤りです。正しくは、

def メソッド名(引数: 引数の型, ...): 返り値の型 = 式 // 返り値の型は省略可能
// { } は要素を順に評価し、最後の要素の評価値を返す式

です。この二つで何が違うかというと、前者は本来省略形でないものも省略形と認識してしまうことによって、余計なことを覚える必要があるという点です。

また、クラス継承において、継承元が一個の場合はextends、二個以上の場合はそれに加えて、withが必要という解説もありますが、これも厳密には正しくありません

class A extends (Super1 [with ....])

が正しい一般形です。Scalaの文法は、一般形を正しく認識すればそれほど複雑ではない部分が多いのですが(プレースホルダ構文は例外的に複雑です)、省略形と一般形とを誤って認識すると途端に覚えることが増えてしまいます。

  • 式を基本として考える

Javaなどから来るとやや異質に感じるかもしれませんが、Scalaでは制御構造も含めてほとんどの「いわゆる文」が式です。式は評価すると必ず(例外が投げられない限り)何らかの値を返します。

たとえば、

if(1 < 3) {
  println("1 < 3")
}

と書いたとき、この式の返す値の型はUnitです。また、

a match {
  case 1 => "1"
  case 2 => "2"
  case 3 => "3"
  case _ => "otherwise"
}

という式が返す値の型はStringです。そして、式の評価値は変数に代入できるので、当然のことながら(match式に型が付く場合)

val b = a match {
  case 1 => "1"
  case 2 => "2"
  case 3 => "3"
  case _ => "otherwise"
}

は基本的にコンパイルを通ります(例外はありますが)。

  • 「関数」と「メソッド」を区別する

これは、コップ本とかScala言語仕様ですら正しく用語を使い分けしていないのが悪いのですが、Scalaにおいて

  • 関数は第一級の値である
  • メソッドは第一級の値でない

という根本的な違いがあります。そして、あるものが関数かどうかを判定するのはとても簡単で、ぶっちゃけていうと、defを使って定義されたもののみがメソッドで、それ以外が関数です。

  • Implicit Conversionは本質的な機能ではない

若干語弊があるのですが、そもそも機能としてはImplicit Parameter(によって実現される機能)が本質的であり、Implicit Conversionはその特殊な形です。より正確には、A => Bという型のImplicit Parameterがあったときに、これはA型からB型へ暗黙に変換ができるとみなしてコンパイラがコードを挿入するというだけの話です。Implicit Conversion怖いという意見をよく見るますが、そもそもそれは本質的なものではないのだ、ということです。

考えながら項目を継ぎ足していったのでイマイチ整理しきれていませんが、まとめとしては、核となる機能と枝葉の機能を区別せよ。そして、枝葉の機能は可能な限り核となる機能への変換としてとらえよ(そうすれば覚えるべきことを最小化できる)、ということになるかと思います。このアドバイスはScalaだけでなく新しい言語を高速に覚える際に役立つと私は思っています。

自分流のプログラミング言語学習法をまとめた感じなので、異論があるかもしれませんが、「個々の機能を丸暗記する」学習法は効率が悪いのだ、ということは強く言いたいです。

これまでの人生で一番、死を身近に感じたときのこと

とても物騒なタイトルがついていますが、別に自殺しかけたとか事件に巻き込まれたという話ではありません。

大学1年生の夏(2002年)の事でした。当時、筑波大学1年生だった自分は、サークル(?)の先輩たちとともに、今ではガルパンの聖地として知られている大洗まで、筑波大学から自転車で日帰りで行くことになっていました。なんでそうなったかはよく覚えていないのですが、たぶん自転車で海まで行こう!といういい加減なノリだったと思います(まあ大学の学部生なんてそんなもんですよね)。ちなみに、自転車とは行ってもママチャリで、しかも片道42kmくらいあるので今思えばなかなかハードな行程でした。

さて、そんなハードな行程にも関わらず、帽子もかぶらず、汗を拭くものも持って来ないという大馬鹿なことをしたのがそもそも間違いでした。集合したときに、帽子をかぶってないこと等について、大丈夫?という旨の心配されたのですが、自分は能天気にも大丈夫だろうと思って随分軽い返事をしたのを覚えています。

そんな感じでハードな行程に見合う準備をせずに大洗への日帰り旅行は始まったのですが、案の定、途中でバテ始めました。先輩たちは気のいい人で、途中でギブアップすればすぐ応じてくれただろうと思うのですが、当時の自分は、サークル(?)に入ってあまり経っていないこともあり、心配をかけたくないという思いでなんとかついていきました。大洗は海水浴客で賑わっている中、とても暑いなーと思ったことが印象に残っています。

なんとかかんとか筑波大学に戻って来ることが出来、解散して帰宅したのですが、問題はその後でした。気がついたらすっかり発汗が止まり、身体が熱っぽくなっていたのでした。途中で水分補給はしたものの、たぶん水分が不足しているのだろうと近くのコンビニでスポーツドリンク2lを買い、がぶがぶ飲んだのですが、汗はちっとも出ないし尿もでない。これは何かおかしいと思って体温計で体温を測ってみると38度超。ここに至ってもまだなんとかなるだろうと思い、さらに水分を補給しながら体温の上昇が止まるのを待ったのですが、ちっとも止まる気配がない。ついには39度を越し、手足のしびれが来るに至って、いよいよこれはヤバイと思い、熱中症について検索しました。すると、どうやら熱疲労と熱射病の間くらいであり、このまま放置すると死ぬ!こんなところで人生が終わるなんて嫌だ!という強い恐怖感にかられ、急いで救急車を呼びました。

結果、熱中症と診断され、医師に「このまま放置していたらおそらく死んでいたよ」と言われ、心底ぞっとしたのを覚えています。そして、何の数値かは忘れたのですが、たぶん臓器の機能に関する何らかの数値が下がるまで入院となったのでした。先輩や友人、弟が見舞いに来てくれたのをよく覚えています。

それ以来、夏に熱中症の前駆症状のような症状が現れたらすぐ気づくようになり、すばやく水分補給とタオルで頭を冷やすなどの対処ができるようになったのでした。怪我の巧妙功名というやつですね。また、それまでも割と死への恐怖は強い方だったのですが、この体験を経て死への恐怖は一層強化されたのでした。結論はないですが、熱中症には気をつけましょうということになるでしょうか。