10 Scala Programming Pitfalls | Javalobby
Scala プログラミングの十個の落とし穴
Scala is great for highly scalable, component-based applications that support
concurrency and distribution. It leverages aspects of object-oriented and functional
programming. This JVM-based language gained most of its clout when it was announced
that Twitter was using it. If used correctly, Scala can greatly reduce the code
needed for applications.
Scala は、非常に scalable であり、並列処理や分散処理をサポートするコンポーネントベース
のアプリケーションに向いたものです。Scala はオブジェクト指向プログラミングや関数プログ
ラミングの aspects をleverages します。この JVMベースの言語は、Twitter がこの言語を使
用するというアナウンスがされたときに最も影響力を獲得しました。正しく使えば、Scala はア
プリケーションに必要となるコードを大幅に縮減 (reduce) してくれるでしょう。
For the Scala programmmer, DZone has gathered these common code-writing pitfalls.
These tips come from Daniel Sobral, a Scala enthusiast who has managed Java software
development projects and participated in the FreeBSD project.
Scala プログラマーにとって、DZone はこのような一般的な code-writing pitfalls (コードを
書くときの落とし穴)を集めています。そこにある tips はJava ソフトウェア開発プロジェクト
をマネージメントしたりFreeBSD プロジェクトに加わったりした Scala の enthusiast である
Daniel Snbral によるものです。
1. Syntactic Mistake
構文的な誤り
Thinking of "yield" as something like "return". People will try:
"yield"を "return"のように考えてしまって次のようにしてしまいがちです:
for(i <- 0 to 10) {
if (i % 2 == 0)
yield i
else
yield -i
}
Where the correct expression is:
正しい式はこうなります:
for(i <- 0 to 10)
yield {
if (i % 2 == 0)
i
else
-i
}
2. Misusage and Syntactic Mistake
Using scala.xml.XML.loadXXX for everything. This parser will try to access external
DTDs, strip comments, and similar things. There is an alternative parser in
scala.xml.parsing.ConstructingParser.fromXXX.
何に対しても scala.xml.XML.loadXXX を使ってしまう。このパーザーは外部の DTDs にアクセ
スしようとし、コメントを剥ぎ取るなどのことを行います。代替パーザーが
scala.xml.parsing.ConstructingParser.fromXXX にあります。
Also, forgetting spaces around the equal sign when handling XML. This:
また、XMLを処理するときにイコール記号の周りにスペースを置くことを忘れてしまう。
val xml=<root/>
really means:
これは次のように解釈されます:
val xml.$equal$less(root).$slash$greater
This happens because operators are fairly arbitrary, and Scala uses the fact that
alphanumeric character must be separated from non-alphanumeric characters by an
underscore in a valid identifier to be able to accept expressions such as
"x+y" without assuming this is a single identifier. Note that "x_+"
is a valid identifier, though.
これは演算子が fairly arbitary なために起きることで、そして Scala はalpha numeric
characters がある正当な識別子中においてはアンダースコアによって non-alphanumeric
characters から分離されなければならないという規則を使って、ひとつの識別子であるという
仮定を置くことなく "x+y" のような式を受理するのを可能にしています。"x_+"
が正当な識別子 (vaild identifier)であることに注意してください。
So, the way to write that assignment is:
ですから、先ほどの代入は次のように記述します:
val xml = <root/>
3. Misusage Mistake
Using the trait Application for anything but the most trivial use. The problem with:
trait Application を anything but the most trivial use (些細なことでないこと
すべて) のために使う。
object MyScalaApp extends Application {
// ... body ...
}
is that body executes inside a singleton object initialization. First, the execution
of singletons initialization is synchronized, so your whole program can't interact
with other threads. Second, JIT won't optimize it, thus making your program slower
than necessary.
このようにしてしまうことの問題点は、その本体がシングルトンオブジェクトの初期化の内部で
実行してしまうことです。第一に、シングルトンの初期化の実行は同期されます (synchronized)
から、あなたのプログラム全体は他のスレッドとやり取り (interact) できません。第二に、
JIT はこれを最適化しません。したがって、あなたのプログラムは必要以上に遅いものになって
しまいます。
By the way, no interaction with other threads means you can forget about testing GUI
or Actors with Application.
ところで他のスレッドとやり取り (interaction) しないということは、あなたが GUI のテスト
や Application を使った Actors を忘れてしまっている可能性があるということを意味します。
4. Misusage Mistake
Trying to pattern match a regex against a string assuming the regex is not bounded:
正規表現 (regex) が束縛されていないことを仮定している文字列に対して、正規表現による
パターンマッチングを試みる:
val r = """(\d+)""".r
val s = "--> 5 <---"
s match {
case r(n) => println("This won't match")
case _ => println("This will")
}
The problem here is that, when pattern matching, Scala's Regex acts as if it were
bounded begin and end with "^" and "$". The way to
get that working is:
これが問題なのは、パターンマッチングのときの Scala の Regex は開始位置と終了位置がそれ
ぞれ "^" and "$" で束縛されているかのように振舞うからです。
動作するやり方はこうです:
val r = """(\d+)""".r
val s = "--> 5 <---"
r findFirstIn s match {
case Some(n) => println("Matches 5 to "+n)
case _ => println("Won't match")
}
Or just making sure the pattern will match any prefix and suffix:
あるいはパターンが任意の prefix や suffix にマッチするようにします:
val r = """.*(\d+).*""".r
val s = "--> 5 <---"
s match {
case r(n) => println("This will match the first group of r, "+n+", to 5")
case _ => println("Won't match")
}
5. Misusage Mistake
Thinking of var and val as fields.
var と val をフィールドのように考えてしまう。
Scala enforces the Uniform Access Principle, by making it impossible to refer to a
field directly. All accesses to any field are made through getters and setters. What
val and var actually do is define a field, a getter for that field, and, for var, a
setter for that field.
Scala は Uniform Access Principle (統一されたアクセスの原則?)を推奨 (enforce) して
います。フィールドを直接参照できないようにすることで、すべてのフィールドに対するアク
セスはすべて下駄と雪駄を通して行われます。val とvar は実際にはフィールドとして定義さ
れていて、そのフィールドに対する下駄と、var に対応するフィールドに対する雪駄も定義
されています。
Java programmers will often think of var and val definitions as fields, and get
surprised when they discover that they share the same namespace as their methods, so
they can't reuse their names. What share the same namespace is the automatically
defined getter and setter, not the field. Many times they then try to find a way to
get to the field, so that they can get around that limitation -- but there's no way,
or the uniform access principle would be violated.
Javaプログラマーは varやvalの定義をフィールドのように考える傾向があって、フィールドが
メソッドのように同じ名前空間を共有しているためにその名前を再利用できないことを発見した
ときにびっくりします。同じ名前空間を共有するということは自動的に下駄と雪駄が定義される
がフィールドは定義されないということです。何度となく彼らは制限を回避してフィールドを得
るための手段を探し求めましたがしかし方法はありませんし。さもなければ uniform
access principle を破ることになってしまいます。
Another consequence of it is that, when subclassing, a val can override a def. The
other way around is not possible, because a val adds the guarantee of immutability,
and def doesn't.
このことのもうひとつの結論は、サブクラス化の際にval が def をオーバーライドできてしま
うということです。val は immutablitiy を追加しますが def は追加しないのでほかの方法は
可能ではありません。
There's no guideline on what to call private getters and setters when you need to
override it for some reason. Scala compiler and library code often use nicknames and
abbreviation for private values, as opposed to fullyCamelNamingConventions for public
getters and setters. Other suggestions include renaming, singletons inside an instance,
or even subclassing. Examples of these suggestions:
何らかの理由で下駄や雪駄のオーバーライドが必要なときに、private な下駄と雪駄を呼び出す
ためのガイドラインはありません。Scala コンパイラーとライブラリコードはしばしばニックネ
ーム (nicknames) と略称 (abbreviation) をprivate な値のために使い、public な下駄や雪駄
のためにはfullyCamelNamingConventions を使います。そのほかの提案には、リネーミング
(renaming)、あるインスタンスの内部にあるシングルトン、サブクラス化さえ挙げられました。
こういった提案の例を挙げましょう:
Renaming リネーミング
class User(val name: String, initialPassword: String) {
private lazy var encryptedPassword = encrypt(initialPassword, salt)
private lazy var salt = scala.util.Random.nextInt
private def encrypt(plainText: String, salt: Int): String = { ... }
private def decrypt(encryptedText: String, salt: Int): String = { ... }
def password = decrypt(encryptedPassword, salt)
def password_=(newPassword: String) = encrypt(newPassword, salt)
}
Singleton シングルトン
class User(initialName: String, initialPassword: String) {
private object fields {
var name: String = initialName;
var password: String = initialPassword;
}
def name = fields.name
def name_=(newName: String) = fields.name = newName
def password = fields.password
def password_=(newPassword: String) = fields.password = newPassword
}
alternatively, with a case class, which will automatically define methods for equality,
hashCode, etc, which can then be reused:
等価性の検査のために自動的に定義されるメソッドを定義する再利用可能な case クラス
や HashCode など
class User(name0: String, password0: String) {
private case class Fields(var name: String, var password0: String)
private object fields extends Fields(name0, password0)
def name = fields.name
def name_=(newName: String) = fields.name = newName
def password = fields.password
def password_=(newPassword: String) = fields.password = newPassword
}
Subclassing サブクラス化
case class Customer(name: String)
class ValidatingCustomer(name0: String) extends Customer(name0) {
require(name0.length < 5)
def name_=(newName : String) =
if (newName.length < 5) error("too short")
else super.name_=(newName)
}
val cust = new ValidatingCustomer("xyz123")
6. Misusage Mistake
Forgetting about type erasure. When you declare a class C[A], a trait T[A] or a
function or method m[A], A is not present at run-time. That means, for instance that
any type parameter will be actually compiled as AnyRef, even though the compiler
ensures, compile time, that the types are respected.
type erasure のことを忘れる。クラスC[A]、trait T[A]、あるいは関数もしくはメソッドの
m[A]を宣言すると、A は実行時には存在していていません。これは任意の型パラメータのインス
タンスは、コンパイル時にその型が期待できるとコンパイラーが保証したとしても実際には
AnyRef としてコンパイルされるためです。
It also means that you can't use type parameter A at compile time. For instance, this
won't work:
これはまた、型パラメーター A をコンパイル時に使えないということでもあります。
たとえば次のコードは動きません。
def checkList[A](l: List[A]) = l match {
case _ : List[Int] => println("List of Ints")
case _ : List[String] => println("List of Strings")
case _ => println("Something else")
}
At run-time, the List being passed doesn't have a type parameter. Also, List[Int] and
List[String] will both become List[_], so only the first case will ever be called.
実行時には、このList は型パラメーターなしで渡されます。また、List[Int] や List[String]
は両方とも List[_] になりますので、最初の case だけが呼び出されることになるのです。
You can get around this, to some extent, by using the experimental feature Manifest,
like this:
以下の例のように実験的な機能 (experimental feature) である Manifest を使うことで
これに対処することができます;
def checkList[A](l: List[A])(implicit m: scala.reflect.Manifest[A]) = m.toString match {
case "int" => println("List of Ints")
case "java.lang.String" => println("List of Strings")
case _ => println("Something else")
}
7. Design Mistake 設計のミス
Careless use of implicits. Implicits can be very powerful, but care must be taken not
to use implicit parameters of common types or implicit conversions between common
types.
不注意な implicits の使用。implicits は非常に強力なものになりえますが、絶対にcommon
types の implicit parameters を使用しないように、また、common types 間の implicit
conversions が発生しないように注意しなければなりません。
For example, making an implicit such as:
例として以下のような implicit を行います:
implicit def string2Int(s: String): Int = s.toInt
is a very bad idea because someone might use a string in place of an Int by mistake.
In cases where there's use for that, it's simply better to use a class:
これはとても悪いアイデアです。なぜなら、間違って Int のところで string を使ってしまう
かもしれないからです。そういった使い方がありうる場合には、単に class を使うほうがよい
のです。
case class Age(n: Int)
implicit def string2Age(s: String) = Age(s.toInt)
implicit def int2Age(n: Int) = new Age(n)
implicit def age2Int(a: Age) = a.n
This will let you freely combine Age with String or Int, but never String with Int.
こうするとあなたは自由に Age と String や Int を組み合わせることができますが、
String に Int を組み合わせることはできなくなります。
Likewise, when using implicit parameters, never do something like:
同様に、implicit parameters を使うときには
次のようなことを行ってはいけません:
case class Person(name: String)(implicit age: Int)
Not only this will make it easier to have conflicts of implicit parameters, but it
might result in an implicit age being passed unnoticed to something expecting an
implicit Int of something else. Again, the solution is to use specific classes.
これは implicit parameters の衝突を起こしやすくするばかりでなく
繰り返しますが、これに対する解決策は特定のクラス群を使うことです。
Another problematic implicit usage is being operator-happy with them. You might think
"~" is the perfect operator for string matching, but others may use it for
things like matrix equivalence, parser concatenation, etc. So, if you are going to
provide them, make sure it's easy to isolate the scope of their usage.
もうひとつの問題のある implicit の使い方はoperator-happy with them にするということで
す。あなたは "~" が文字列マッチングに対する演算子以外の何者でもないと考える
かもしれませんが、他の人はそれを行列の等価性 (equivalence) のためやパーザーの連結
(concatnation) などに使っているかもしれません。ですから、あなたがそういった機能を提供
しようと考えているのなら、その使い方をしているスコープを isolate しやすいようにしてお
きましょう。
8. Design Mistake 設計上の誤り
Badly designing equality methods. Specifically:
等価性のチェックを行うメソッドの badly な設計。特に:
* Trying to change "==" instead of "equals" (which gives you "!=" for free).
"equals" を "==" に代えるようにしましょう (こうすると "!=" がタダでついてきます)。
* Defining it as
def equals(other: MyClass): Boolean
instead of
のように定義するのではなく、次のようにします
override def equals(other: Any): Boolean
* Forgetting to override hashCode as well, to ensure that if a == b then
a.hashCode == b.hashCode (the reverse proposition need not be valid).
a == b のときに a.hashCode == b.hashCode であることを保証するための
hashCode のオーバーライドを忘れる (逆方向の proposition は必須で
はありません)
* Not making it commutative: if a == b then b == a. Particularly think of
subclassing -- does the superclass knows how to compare against a subclass
it doesn't even know exists? Look up canEquals if needed.
a == b のときに b == a になるように commutative にしない。
スーパークラスはサブクラスに対してどのように比較されるのか、そのサブクラスが
存在するかどうかをスラ知らないのに知っているでしょうか?
必要であれば canEquals を look up する。
* Not making it transitive: if a == b and b == c then a == c.
a == b であり b == c であるときに a == c であるような transitive にしない。
9. Usage Mistake 使い方の間違い
On Unix/Linux/*BSD, naming your host (as returned by hostname) something and not
declaring it on your hosts file. Particularly, the following command won't work:
Unix/Linux/*BSD では、自分のホストを(hostname で返されるように)命名しますが
hosts files にそれを記述しません。
特に、次のようなコマンドは動作しません:
ping `hostname`
In such cases, neither fsc nor scala will work, though scalac will. That's because fsc
stays running in background an listening to connections through a TCP socket, to speed
up compilation, and scala uses that to speed up script execution.
このような場合、scalc では動作しますがfsc でも scala も動作しません。これは、fsc がバ
ックグラウンドで動作しつづけていて、高速化のために TCPソケットのコネクションを listening
しているからです。また、scala はこれをスクリプト実行の高速化のために使用しています。
10. Style Mistake スタイルの間違い
Using while. It has its usages, but, most of the time, a solution with
for-comprehension is better.
while を使う。while には while の使い道というのがありますが。ほとんどの場合は
for-comprehension を使った solution のほうが better です。
Speaking of for-comprehensions, using them to generate indices is a bad idea too.
Instead of:
for-comprehensions については、それを添字を生成するために使うことはこれまた
良くないアイデアです。
def matchingChars(string: String, characters: String) = {
var m = ""
for(i <- 0 until string.length)
if ((characters contains string(i)) && !(m contains string(i)))
m += string(i)
m
}
のようにするのではなく、次のようにします:
Use:
def matchingChars(string: String, characters: String) = {
var m = ""
for(c <- string)
if ((characters contains c) && !(m contains c))
m += c
m
}
If one needs to return an index, the pattern below can be used instead of iterating
over indices. It might be better applied over a projection (Scala 2.7) or view (Scala
2.8), if performance is of concern.
もし添字を返す必要があるのなら、以下に示したパターンを添字に対して iterarting over す
る代わりに使えます。性能を重視する場合にこれは projection (Scala 2.7) を適用したりviwe
(Scala 2.8) を適用するよりも better な可能性があります。
def indicesOf(s: String, c: Char) = for {
(sc, index) <- s.zipWithIndex
if c == sc
} yield index