駄文生産所 このページをアンテナに追加 RSSフィード

2011-06-11

ここがヘンだよScala言語

Scalaを触ったり言語仕様を眺めた上で、ストレスを感じたものをいくつか挙げる。おそらくコミュニティでの議論は既に終了しているであろうし、無意味な作業に感じられて仕方がないが、某所でまとめると言ってしまったので、我慢して進める。

突撃されると面倒なので一応述べておくが、別にScalaダメだとか、Scalaが使えないとか言いたいわけではない。この程度の訓練は必要なのだ、ということだ。


追記

びっくりするほどのバカだと思われている節があるが、ScalaJavaの思想を知った上で、あえて記述している部分も多分にある。一応。

捉え違いしている部分や、Scala的な考え方が知りたい方は、コメントをご覧になるのが良いでしょう。


if式の返り値

val v1 = if (1 < 2) {"a"} 
println(v1)

val v2 = if (1 < 2) {"a"} else {"b"}
println(v2)

v1はUnit、v2は"a"。else節が無い場合、「最後に評価された式を、返り値とする」というルールから外れる。

2.8.1、2.9.0等、現行の版では、v1、v2ともに"a"が返る。ルール通りの動きで問題はない。


除外インポート

import scala.collection.mutable.{_, Map => _, Set => _}

サンプルはMap、Set以外の全クラスインポート。ここで、"_"にワイルドカードとしての役割と、消去の役割をもたせている。こんなのよく通したな。


typeと別名インポート

typeがあるなら別名インポートはいらないのでは?


forのネスト

for(i <- 0 to 10; j <- 0 to 10; k <- 0 to 10) {
	print(i + ":" + j + ":"+ k)
}

ループが一つなのか、三つなのか、ぱっと見で分からない。必要だったのか?


for内包表記中のifとif式

for(n <- List(1,2,3,4,5) if n % 2 == 1) yield print(n + " ") // OK

val n = 3
val = if n % 2 == 1 // コンパイルエラー
}

内包表記中のifと、if式は違う。


forとforEach

クロージャとforEachメソッドをサポートするなら、for式は必要ないのでは?

yieldは好みじゃない。特例だから。


タプルのインデックス

コレクションは0オリジン、タプルは1オリジン


overrideの有無

  • 通常のメソッドの上書きには、overrideキーワードが必要である。
  • 抽象メソッドの上書きには、overrideキーワードが必要ない。

どちらかにして欲しい。


インスタンス生成の責務

わざわざクラスオブジェクトとしての振る舞いをobjectに括りだしたのだから、インスタンス生成の責務はobjectに渡せば良かったのでは?


インスタンス生成時のnewの有無

class, caseクラスobject。場合によってnewを付けたり、外したり。


コンストラクタ

基本コンストラクタと、thisを用いて定義するコンストラクタ。分ける必要があったのか?


プレースホルダ

"ACBED".sortWith(_ > _) 

複数の引数を取る場合、異なるオブジェクトに同じ名前を付けることになり、順番に依存する。


Mutatorの定義

class Foo {
  var bar: Int = _
  
  def bar_=(value: Int) {
    this.doSomething
    this.bar = value
  }
}

特別なメソッドの書き方。


似ているキーワード

None、Nothing、Null、null、Nil。一つ一つ意味と用法を覚える。


mutableなSet,Mapと、immutableなSet,Map

性質の異なるものを、わざわざ同名にする必要はあったのか?


コロンで右結合

通常の左結合の演算子が混じると面倒。


パラメータ境界

foo[A <: T]、bar[A >: T]、boo[A <% T]。このときコロンで右結合は関係あるんだろうか?

UML的なsuper-subの矢印の方向とも逆なので、Scala用の解釈として覚える。

パーサーやらの都合は知らないが、foo[ A.isSubtypeOf[T] ]、bar[ A.isSupertypeOf[T] ]、とかのほうが読み下すには良いと思う。


暗黙の型変換

implicitにやるよりは、必要になった時点でtoString()のようなフレームワークで指定したconverterメソッドを呼び出す方が好み。言語機能に同じような役割の新要素を追加するよりマシ。


notメソッド

unless文をサポートしないなら、Booleanにnotメソッドくらいは装備しておいて欲しい。


コンパニオンの参照

class A {
  def companion = A  // 名前を直に指定するのはカッコ悪い
  def foo = this.companion.defaultValue
}

object A {
  val defaultValue = 10
}

コンパニオンを取得するメソッドくらい装備しておいて欲しい。


unsignedがない

32ビットunsignedなら、4ByteをByteでとってLongに変換。低いレイヤやネットワーク関連で面倒な思いをしている人も多いことだろう。


おわり

キリがなく、面倒なのでやめる。触れたのは氷山の一角だ。省略可能な部分、型周辺、アノテーション周辺、"/:"や":\"などの演算子周りには、おそらく気持ちの悪い部分がゴロゴロ転がっている。

一貫性から外れ、特例を知らなければならない件が散見される。そもそも覚えるべきことが多い。現状、Scalaはそこそこ覚悟のいる環境だと思う。

「訓練されたScalaプログラマ」には複雑さは見えないんじゃないか』というまつもと氏の言には大いに同意する。まぁ慣れれば助数詞のように気にならなくなるのだろう。


ふかふか 2011/06/11 23:04 なんというか、フィルターがおかしいというか、困ってから困ればいいのに。
for文がいらないというのには同意。

ぬしぶぬしぶ 2011/06/11 23:10 すげぇ細かくてすいませんが、返り値が()になるには、
val v1 = if (1 > 2) {"a"}
ですね。

kaminamikaminami 2011/06/11 23:53 >ぬしぶさん
これを試した処理系が古かった(2.7.7)です。2.8移行はコメントいただいた通りになりますね。修正します。

kmizushimakmizushima 2011/06/12 00:28 ようやく具体的な論点を提示していただきありがとうございます。それでは、反論を開始しましょう。まず、結論として、kaminamiさんは、Scalaにおける基本ルールを中途半端にしか理解されていません。そのせいで、特例で無い事も特例としてとらえてしまっているのでしょう(中には的を射た指摘もありますが)。以下、一つ一つ説明していきます。

> v1はUnit、v2は"a"。else節が無い場合、「最後に評価された式を、返り値とする」というルールから外れる。

"「最後に評価された式を、返り値とする」"なんてルール、一体Scalaのどこにありましたか?ブロック式{e1; e2; ... en}において、ブロック式の返り値をenとするというルールこそあれ、一般的にそんなルールは存在しませんが。また、そもそも、「最後」って何の最後か考えられたことはありますか?ちなみに一つクイズですが、while式の返り値型は何であるべきでしょうか。

> 内包表記中のifと、if式は違う。

for式は「それ全体で」一つの「構文」ですから、意味が違う事は特例に当たりません。キーワードを流量することの是非はあるでしょうが、似た働きを持つものに異なるキーワードを与える方が混乱を招くでしょう。

> コレクションは0オリジン、タプルは1オリジン。

これは関数型言語の慣習による歴史的な事情がありますが、コレクションという可変長のものと、タプルという固定個のフィールドがあるクラスを同じ土俵で比較するのはアンフェアです。

> クロージャとforEachメソッドをサポートするなら、for式は必要ないのでは?
> yieldは好みじゃない。特例だから。

はっきり言ってお話になりません。for式の構文の一般形=ルール(特例ではなく)をまず理解されるのが先だと思われます。ちなみに、for式はScala自体や周辺ライブラリを含め、色々なところで使われています。for式を単なる拡張for文レベルのものだと勘違いされていませんか?

> わざわざクラスのオブジェクトとしての振る舞いをobjectに括りだしたのだから、インスタンス生成の責務はobjectに渡せば良かったのでは?

基本認識が間違っています。Scalaでは全てのメソッド呼び出しにレシーバが必要というのが基本ルールなので、本来レシーバが無いメソッドの所属先としてobjectがあるだけです(companion objectにおいては若干事情が異なりますが)。

> class, caseクラス、object。場合によってnewを付けたり、外したり。

objectは定義された値であり、アクセスされた時点で生成されるものですからnewが要らないのは当たり前です。case classはapplyメソッドを持ったcompanion objectが生成されるので、newが省略できます(case class自体にnewを省略する特殊機能があるわけではありません)。それ以外のclassはnewが必要です。

> 通常のメソッドの上書きには、overrideキーワードが必要である。
> 抽象メソッドの上書きには、overrideキーワードが必要ない。

overrideは既存の定義を「上書き」するものですから、そもそも実体の無い抽象メソッドを「overrride」する必要は無いでしょう。

> 複数の引数を取る場合、異なるオブジェクトに同じ名前を付けることになり、順番に依存する。

認識が間違っています。プレースホルダー構文はそのようなものではありません。

> 特別なメソッドの書き方。
これについては、特別であることは否定しません。ただし、Bertrand Meyerの言うところの統一アクセス原則を実現するためには必要な機能です。

> None、Nothing、Null、null、Nil。一つ一つ意味と用法を覚える。

はっきり言っていいがかりレベルですね。どんな言語だろうが、異なる実体に対しては別の名前を付けて、それぞれを区別して意味を覚えるものです(Smalltalkでは異なるメソッドに対して、それぞれの意味を知る必要は無いのでしょうか?)。表記が似ているように(自分には)見えるから混乱する、というのならそれを正直に言われた方がよいかと。

> 基本コンストラクタと、thisを用いて定義するコンストラクタ。分ける必要があったのか?

コンストラクタはオーバーロードできるべきではない、という意見ですか?Javaではコンストラクタをオーバーロードできますし、Scalaでそれが可能なことがそれほど不思議ですか?

> 性質の異なるものを、わざわざ同名にする必要はあったのか?

機能としては類似しているが、性質が異なる部分があるからわざわざパッケージ名を分けてあるのですが。immutable.ImmutableMapとか冗長な名前がお好みですか?

> 通常の左結合の演算子が混じると面倒。

実験的に面倒になるような書き方をしなければ、普通そんな妙な現象にはなりませんが。

> foo[A <: T]、bar[A >: T]、boo[A <% T]。このときコロンで右結合は関係あるんだろうか?

関係ありません。逆になんでそう思われたのかが不思議です。

> UML的なsuper-subの矢印の方向とも逆なので、Scala用の解釈として覚える。

> パーサーやらの都合は知らないが、foo[ A.isSubtypeOf[T] ]、bar[ A.isSupertypeOf[T] ]、とかのほうが読み下すには良いと思う。

「A <: B」は、AはBのサブタイプである、を表す、型システムの分野ではそれなりに一般的な表記です。あと、「UMLと異なる〜」とか、どう見ても言いがかりですが。

> implicitにやるよりは、必要になった時点でtoString()のようなフレームワークで指定したconverterメソッドを呼び出す方が好み。言語機能に同じような役割の新要素を追加するよりマシ。

意味がわかりません。implicit conversionの言語機能がどのようなものか理解されていますか?

> unless文をサポートしないなら、Booleanにnotメソッドくらいは装備しておいて欲しい。

特例が少ない言語の方がいいのに、「unless文」のような「特例」を増やした方がいいですか?というか、unlessくらいメソッドとして簡単に書けますよ。

def unless[A](cond: Boolean)(body: => A) = {
if(!cond) body
}

> コンパニオンを取得するメソッドくらい装備しておいて欲しい。

要望として否定しませんが、それは結果として「覚えること」を増やすことにつながります。それでいいですか?

> 32ビットunsignedなら、4ByteをByteでとってLongに変換。低いレイヤやネットワーク関連で面倒な思いをしている人も多いことだろう。

unless(3 < 2) {
println("3 < 2 == false")
}

unless(2 < 3) {
println("2 < 3 == false")
}

> unsignedがない
Scalaは、プリミティブ型に相当する型(AnyValのサブクラス)は、Javaの型と直接にマッピングできる事が重要なルールになっています。そこで、うかつにunsignedのような、Javaに直接マッピングできない型を導入した場合、どのような結果になるか考えてみたことはありますか?

結構きつい書き方になりましたが、正直に言って、中途半端な認識で言語をdisる態度は性質が悪いです。

kaminamikaminami 2011/06/12 01:12 >kmizushimaさん。解説ありがとうございます。
おかげさまで、私が捉え違いをしてた部分と、Twitter上でもここでも私の意図がまるで理解されていないことの両方が分かりました。
以後、会話が成立するとはとても思えません。
解答編は是非他所でやっていただきたく思います。その方が他のユーザさんにも有益でしょう。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kaminami/20110611/p1