久しぶりの投稿です。
最近何をやっていたかというと、はてなへの移行作業を行っていました。
移行先:http://d.hatena.ne.jp/dreamscrapround/
理由やら、どーやって移行したか、とかは下記の記事を参照。
http://d.hatena.ne.jp/dreamscrapround/20110629/1309381231
とりあえず、場所こそ移りましたが、投稿自体は同じような内容を続けるつもりではあります。
ブログをはてなに移行しました
scalaで出来ることを並べてみる:Predef
scalaには一部メソッドだけでどのクラスからも呼び出せるメソッドがあります。
サンプルコード
■PredefMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
/**
* Predef用メインクラス
*
* @author Fukushi-
*/
object PredefMain {
/**
* Predefテスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
// requireメソッド確認
// trueの場合何もしない
require(true);
// falseの場合java.lang.IllegalArgumentException発生
try {
require(false);
} catch { case ex: IllegalArgumentException => ex.printStackTrace() }
// printlnメソッドを実行すると自動的にConsole.printlnメソッドにマッピング
println("PredefMain");
}
}
実行結果
java.lang.IllegalArgumentException: requirement failed
at scala.Predef$.require(Predef.scala:145)
at jp.gr.kmtn.scalatutorial.grammertips.PredefMain$.main(PredefMain.scala:21)
at jp.gr.kmtn.scalatutorial.grammertips.PredefMain.main(PredefMain.scala)
PredefMain
解説
サンプル中で、require(boolean)というメソッドを呼び出していますが、このメソッドはscalaであればどんなソースからもメソッド呼び出しのみで使用できるメソッドです。
与えた引数によって例外を発生させることが出来ます。
コンストラクタでの初期化で不正な値を弾く際などに使えますね。
で、上記のメソッド群は「scala.Predef」というobjectで定義されています。
実はprintlnメソッドが自動的にConsole.printlnメソッドにマッピングされるのも
Predefobjectの動作によるものでした。
他にもJavaでいうプリミティブ型の変換処理、文字列変換、コンソールからの入出力などが揃っています。
さすがに前回のリッチラッパーとは違い、Predefを使用して好きな機能を追加。。。
とは行かないでしょうが、無駄なメソッドを作らずに済む点では使いやすいかと。
scalaで出来ることを並べてみる:演算子表現=メソッド表現&リッチラッパー
なんですが、イマイチそれだけ言われても実感できないので、
試してみました。
ついでにリッチラッパーの確認もやってました。
サンプルコード
■OperatorMethodMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
/**
* 演算子/メソッド共通確認+リッチラッパー確認テスト用メインクラス
*
* @author Fukushi-
*/
object OperatorMethodMain {
/**
* 演算子/メソッド共通確認+リッチラッパー確認テスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
// 演算子動作確認
val two = 2
val five = 5
val nine = 9
// 演算子表現確認
Console.println("---Case 1-1 ---")
Console.println(two + five)
Console.println(nine - five)
// メソッド表現確認
Console.println("---Case 1-2 ---")
Console.println(two.+(five))
Console.println(nine.-(five))
// メソッド動作確認
val str = "Two,Five,Nine"
// 演算子表現確認
Console.println("---Case 2-1 ---")
val strArray = str split ","
strArray.foreach(Console.println(_))
// メソッド表現確認]
Console.println("---Case 2-2 ---")
str.split(",").foreach(Console.println(_))
// リッチラッパー用メソッド確認
Console.println("---RichWrapper ---")
Console.println(nine max five)
Console.println(nine.max(five))
}
}
実行結果
で、実行結果は下記のようになります。
---Case 1-1 ---
7
4
---Case 1-2 ---
7
4
---Case 2-1 ---
Two
Five
Nine
---Case 2-2 ---
Two
Five
Nine
---RichWrapper ---
9
9
コード解説
上のCase1-Xから見ていきましょう。
Case1-1ではInt型の変数を + で連結して加算を行っています。
Case1-2では、Int型変数のメソッド「+」を呼び出して加算を行っています。
Console.println(two + five) → Case1-1、「+」を演算子形式で記述
Console.println(two.+(five)) → Case1-2、「+」をメソッド形式で記述
Case1-1と、Case1-2の表現は等価です。
つまり、演算子のように見える「+」もメソッドでしかないことがわかりますね。
後、実際にInt型変数のメソッド一覧(Eclipseの補完一覧)を見てみると
演算子と同じメソッドが存在していることがわかります。
次は、Case2-Xの説明です。
Case1-Xとは逆に、splitメソッドを演算子形式で記述しています。
val strArray = str split "," → Case2-1、「split」を演算子形式で記述
val printArray = str.split(",") → Case2-2、「split」をメソッド形式で記述
Case2-1、Case2-2の表現も等価です。
つまりはScalaでは下記の2つのことが言える、となりますね。
1.演算子は存在せず、全てメソッドで構成されている
2.メソッドはJavaでいう演算子形式と、メソッド形式のどちらでも記述可能
Javaと書き方自体は変えずに、背後で動く機構は統一されているようですね。なかなか新鮮。
後、最後にリッチラッパーについて。
下記のコードで、Int型に対してmaxというメソッドを使用しています。
Console.println(nine max five)
Console.println(nine.max(five))
なんですが、Int型にはmaxというメソッドは存在しません。
maxというメソッドが定義されているのは、「scala.runtime.RichInt」というクラスです。
下記のコードの中で、maxメソッド実行時に暗黙の型変換(Int→RichInt)がおこなわれてるため、
Int型の変数が直接maxメソッドを呼び出せるようです。
どういう仕組みなのかなぁ。。と思ってみてクラス構造を見てみた所、
「scala.runtime.ScalaNumberProxy」、「scala.runtime.ScalaNumberProxy」クラスが
リッチラッパーに絡んでいる模様。
更に背後には、「scala.Proxy」トレイトや、「scala.Proxy.Typed」トレイトが絡んでいる模様。
自分で今回のリッチラッパーのように既存のクラスに対して機能を追加できる、
追加機能の実行時にのみ暗黙的に型変換されるため
余計な考慮は不要というコードが作れたら面白そうですよね。
ただ、それについてはまたの機会に。
scalaで出来ることを並べてみる:Applicationトレイト
現在、シングルトンオブジェクトの中にmainメソッドを作成して
その中に実際の処理を書いていますが、
Applicationトレイトを実装したコードを使うと、mainメソッドも省略できます。
■ApplicationTraitMain.scala
object ApplicationTraitMain extends Application {
val immutableMapFirst = Map(1 -> "One", 2 -> "Two")
val immutableMapSecond = immutableMapFirst + (3 -> "Three")
Console.println(immutableMapSecond.toString);
def getName(): String = { "name" }
}
処理内容的には前回の記事と同じです。
ですが、mainメソッドではなく、直接クラス定義に処理を記述しています。
Applicationトレイトを継承していると上記のような記述が可能です。
で、実際に実行した結果も下記のようになり、同じです。
■実行結果
Map(1 -> One, 2 -> Two, 3 -> Three)
尚、この記述の実行タイミングはmainメソッドとは違い、『クラスのロードタイミング』になっています。
#下記のキャプチャより
尚、前回のコードを用いて同じ確認を行うとmainメソッドを呼び出して実行しているため、
実行しているタイミングは明示的に違うようです。
実際にApplicationトレイトは何に使えるかというと、
小規模なプログラムを記述するときにmainメソッドを省略できるということと、
後は、『クラスロード時に呼ばれる』内容のため、クラスロード時の初期化を書くのがいいのかもしれません。
Javaでいうstaticブロックのようなノリでしょうか。
実際に、Applicationトレイト継承シングルトンオブジェクトに他のメソッドを定義することも可能でした。
#サンプルコードの「getNameメソッド」
ともあれ、それなりに規模が出てきた時にまた用途を試してみる必要がありそうです。
scalaで出来ることを並べてみる:Map
JavaやC#においてはMapオブジェクトというのは後で何かを追加して使いまわすために
用いられる『ミュータブルオブジェクト』という扱いが基本です。
ですが、scalaのような関数型言語では『イミュータブルオブジェクト』として扱うのが基本だそうな。
実際、Mapにも「scala.collection.mutable.Map」と、「scala.collection.immutable.Map」として
2種類クラスツリーが存在していています。
クラス名称的には微妙にわかりにくい気もしますが。
で、動作も下記のように違ってきています。
■ImmutableMapObjectMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
import scala.collection.immutable.Map
/**
* イミュータブルMapテスト用メインクラス
*
* @author Fukushi-
*/
object ImmutableMapObjectMain {
/**
* イミュータブルMapテスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
val immutableMapFirst = Map(1 -> "One", 2 -> "Two")
val immutableMapSecond = immutableMapFirst + (3 -> "Three")
Console.println(immutableMapSecond.toString);
}
}
■MutableMapObjectMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
import scala.collection.mutable.Map
/**
* ミュータブルMapテスト用メインクラス
*
* @author Fukushi-
*/
object MutableMapObjectMain {
/**
* ミュータブルMapテスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
val mutableMapFirst = Map[Int, String](1 -> "One", 2 -> "Two")
mutableMapFirst += (3 -> "Three")
Console.println(mutableMapFirst.toString);
}
}
実行結果はどちらも下記のようになります。
■実行結果
Map(3 -> Three, 1 -> One, 2 -> Two)
イミュータブルなMapの場合、+メソッドを使って新しいMapを生成する形で要素を追加。
ミュータブルなMapの場合、+=メソッドを使って新しい要素を追加する形になります。
イミュータブルMapの場合、既存のオブジェクトに影響を与えないことが保障されますが、
要素の追加を行う度に新規Mapが生成されるため、下手すると性能問題になるかもしれません。
ミュータブルMapの場合、既存のオブジェクトに追加されるため性能的には優れそうですが、
いつの間にか状態が変わっている可能性が出てきます。
この辺りを考慮して使い分けるのがよさそうです。
何にしても、使い分けられるのはいいですね。
scalaで出来ることを並べてみる:タプル
JavaやらC#でプログラムを書いていると、『返り値を複数返したい』ケースが出てきます。
特に、違う型の返り値を返したい場合が厄介です。
返り値を返すためだけにBeanクラスを作ったりする。。。というのは面倒ですからね。
・・・まぁ、返り値を複数返したい時点でクラス/メソッド設計が微妙という突っ込みはありますが、
それを言ってしまうと本末転倒なのでここでは省略します。
scalaでは返り値を複数返したい場合、Tupleという型があります。
一言でいえば、異なる型のインスタンスを格納可能なListという感じです。
イミュータブルで、異なる型のインスタンスを混在して格納出来て、型指定も可能。
とりあえず使ってみたサンプルは下記の通りです。
■TupleMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
/**
* タプルテスト用メインクラス
*
* @author Fukushi-
*/
object TupleMain {
/**
* タプルテスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
val tuplePair = ("Arg1", 2)
Console.println(tuplePair);
Console.println(tuplePair.getClass());
Console.println(tuplePair._1);
Console.println(tuplePair._2);
val tuplePairGenerics: Tuple2[Int, String] = (100, "Arg2")
Console.println(tuplePairGenerics);
Console.println(tuplePairGenerics.getClass());
Console.println(tuplePairGenerics._1);
Console.println(tuplePairGenerics._2);
val tupleQuintet = ("Args1", 2, 3, "Args4", "Args5");
Console.println(tupleQuintet.getClass());
}
}
で、実行結果は下記の通り。
■実行結果
(Arg1,2)
class scala.Tuple2
Arg1
2
(100,Arg2)
class scala.Tuple2
100
Arg2
class scala.Tuple5
どうやら、指定した引数の数に応じてTuple2、Tuple3...という別々の型が用意されているようです。
いくつまであるかは興味深い所ですが、そんな増える時点で他の所を疑うべきなのは確かですね。
scalaで出来ることを並べてみる:シンボルリテラル
scalaでJavaに無い概念として、「シンボルリテラル」というものがあります。
言ってしまうと指定された文字列を持つラッパーみたいなもので、
同じ引数を指定されたシンボルリテラルはメモリ上で同じ参照を示すことが保障されます。
とまぁ、これだけかいてもわからないので、とりあえずサンプルコードと実行結果を。
■SimbolLiteralMain.scala
package jp.gr.kmtn.scalatutorial.grammertips
/**
* シンボルリテラルテスト用
*/
object SimbolLiteralMain {
/**
* シンボルリテラルテスト用プログラムエントリポイント
*/
def main(args: Array[String]): Unit =
{
val simbolFirst = 'Simbol
Console.println(simbolFirst);
Console.println(simbolFirst.getClass())
val simbolSecond = 'Simbol
Console.println(simbolSecond)
Console.println(simbolSecond.toString())
Console.println(simbolSecond.getClass())
Console.println(simbolSecond.name)
}
}
このコードを実行すると下記の結果になります。
'Simbol
class scala.Symbol
'Simbol
'Simbol
class scala.Symbol
Simbol
実際に同じシンボルリテラルは同じオブジェクトを使用していることが分かります。
(simbolFirstとsimbolSecondは同じオブジェクトID)
注意すべきは、「シンボルリテラル」≠「文字列リテラル」ということです。
シンボルリテラルは文字列とは比較できません。
そもそも型が違うため、マップで比較することも出来ません。
そのため、シンボルリテラル中の文字列リテラルを使用する際にはnameフィールドを参照しましょう。
nameフィールドで文字列と簡単に比較できるため、
プログラム内部での定数取り回しの記述はかなり少なく出来る。。。のかな?