ログイン
編集不可のページディスカッション情報添付ファイル
"alstamber/Scala"の差分

MMA
2と3のリビジョン間の差分
2013-03-08 07:44:20時点のリビジョン2
サイズ: 20970
編集者: alstamber
コメント:
2013-03-08 07:51:03時点のリビジョン3
サイズ: 21081
編集者: alstamber
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 8: 行 8:
 * コップ本19章ぐらいまで読んだのでその辺りまでとりあえずまとめたい(無理か)

なにこれ

  • コップ本にだいたい則ってScalaについてまとめたもの。
  • Scala勉強会とかで使いたい。
  • Scala処理系が入っている前提。
  • コップ本19章ぐらいまで読んだのでその辺りまでとりあえずまとめたい(無理か)

とりあえず触ってみる

  • scalaコマンドを実行するとインタプリタが立ち上がる。

   1 scala> 1+1
   2 res0: Int = 2

   1 scala> res0 * 5
   2 res1: Int = 10
  • 出力にはprintlnメソッドを使う。
    • printlnメソッドはUnit型の唯一のメンバである()を返す→Javaのvoidに相当

   1 scala> println("Hello, world!")
   2 Hello, world!

変数の定義

   1 scala> val msg = "Hello, world!"
   2 msg: String = Hello, world!
  • 上の定義の時にわざわざ型名(String)って書かなかった
  • 型が明らかなときは型名を書かなくてもいい→型推論

  • もちろん書いてもいい
    • 変数の後ろにコロンを書いて型名

   1 scala> val msg2:String = "Hello, world!"
   2 msg2: String = Hello, world!

varとval

  • 変数を定義するときにはvarとvalというキーワードが使える
    • varは何度でも変更できる
    • valは一度しか代入できない

   1 scala> val msg = "Hello, world!!"
   2 <console>:8: error: reassignment to val
   3        msg = "Hello, World!!"
  • varはなるべく使わないほうがいい
    • 変数の値をいつでも変えられるというのは強力だけど、同時にどこで変数の値が変わってしまったのかわかりにくくなる
    • 極力valを使い、どうしてもパフォーマンスなどでvarのほうが良い時だけvarを使う。

関数定義

   1 def 関数名(引数名:引数型, 引数名:引数型, ...):戻り値型 = 関数の式
  • 具体的な例

   1 def hello(name:String):String = {
   2   return "Hello! " + name
   3 }
  • returnは省略できる
    • 最後の式の結果が返される

   1 def hello(name:String):String = {
   2   "Hello! " + name
   3 }
  • returnを省略して、かつ戻り値の型が明らかなら戻り値の方も省略できる

   1 def hello(name:String) = {
   2   "Hello! " + name
   3 }
  • 一行ならそもそも中括弧もいらない

   1 def hello(name:String) = "Hello! " + name

単独で動くプログラムをつくってみる

  • テンプレ
    • 今はおまじないだと思いましょう
    • argsはStringの配列になっていてmainメソッドの引数です。

   1 object hoge
   2   def main(args:Array[String]) = {
   3     //メインの処理
   4   }
   5 }
  • かけたらコンパイルします

scalac hoge.scala
  • 実行します

scala hoge
  • scalacじゃなくてfscを使うと便利です
    • fscを使うと2回目以降コンパイルが速くなる

Scalaらしいループ

  • argsの中身を順番に表示するプログラムを書いてみる
  • こんなプログラムを書いてみよう
    • scalaでは配列の添字は丸括弧
    • インクリメントの演算子がないのでi += 1で我慢する
    • 行末のセミコロンはいらない

   1 var i = 0
   2 while(i < args.length) {
   3   println(args(i))
   4   i += 1
   5 }
  • このプログラムは良くない
    • varがあるから
  • 配列にはforeachメソッドというのが用意されている
    • 関数を引数にとってその関数を配列の要素に対して実行する

   1 args.foreach((arg:String) => println(arg))
  • argの型はStringなのはわかりきってるのでargの型名は省略できる

   1 args.foreach(arg => println(arg))

配列

  • 同じ型しか要素にとれない
  • valで宣言しても要素の変更は自由

配列の生成

   1 val hoge = Array[String](3)

配列への代入と参照

   1 hoge(2) = "kyoko"
   2 println(hoge(2))

便利な配列の生成方法

   1 val hoge = Array("Akari", "Kyoko", "Yui", "Chinatsu")

リスト

  • 同じ型しか要素にとれない
  • 要素の変更は(通常)不可能
    • mutableなリストというのが特別に用意してあって、それを使えば良い。もちろん推奨はされない。

リストの生成

   1 val hoge = List(2, 3, 5, 7)

リストの結合

   1 val hoge = List(2, 3)
   2 val fuga = List(5, 7)
   3 val piyo = hoge ::: fuga

リストの先頭に要素を追加

   1 val hoge = List(3, 5, 7)
   2 val fuga = 2 :: hoge
  • これは実は下の糖衣構文になっている。つまりListの::というメソッドを呼び出してる

   1 hoge.::(2)
  • ::や:::のことをconsという

タプル

  • 違う型の要素も持てる
  • 要素の変更は不可能
  • 言語の制約上22個しか要素が持てない

タプルを作る

   1 val hoge = (1, "Kyoko", 2, "Akari")

タプルを使う

   1 println(hoge._2)  // Kyoko と表示される
   2 
  • タプルの要素番号はHaskellなどの慣習から1番スタートになっている

クラス

  • Scalaはオブジェクト指向言語なのでクラスを持っている

   1 class Hoge {
   2   var hoge = 1 //フィールド
   3   var fuga = 3 //フィールド
   4 
   5   def myMultiple() = hoge * fuga //メソッド
   6 }
  • Javaとかと同じようにnewでクラスからオブジェクトを生成できる

   1 val a = new Hoge()
  • オブジェクトをval宣言の変数に代入しても、そのオブジェクトのフィールドがvar宣言されていれば再代入できる

   1 a.hoge = 2  // OK
   2 

メソッド

  • メソッドの引数は勝手にvalで定義される
    • 勝手に変えられないようにするため
  • 戻り値がUnitのメソッドは=を省略できる。ただし=を省略すると中括弧を省略できない。
  • メソッドをprivateにするには次のように記述する

   1 private def hello() = "Hello, World!"

シングルトンオブジェクト

  • Scalaは徹底したオブジェクト指向
    • オブジェクトに属していないものの存在なんて許せない!!!!
    • なのでstaticがありません
  • そのかわりにシングルトンオブジェクトというのがある

シングルトンオブジェクトの定義

   1 object Hoge {
   2   // あーだこーだ
   3 

シングルトンオブジェクトの性質

  • シングルトンオブジェクトはコードが実行されるときに自動的に生成される
  • またあるスコープの中でひとつしかないことが保証される
    • 多分後述
  • 先ほど「単独で動くプログラムをつくってみる」の所でobjectうんたらと書いたのは、プログラム実行開始時にシングルトンオブジェクトを生成させてその中のmainメソッドを実行するというふうにScalaが仕組まれているから。

コンパニオンクラスとコンパニオンオブジェクト

  • シングルトンオブジェクトと同じ名前のクラスを定義するとそれぞれコンパニオンオブジェクトとコンパニオンクラスと呼ばれるようになって、対で扱われるようになる。
  • コンパニオンオブジェクトからはコンパニオンクラスにアクセスすることができる。
  • コンパニオンオブジェクトはシングルトンオブジェクトなので、プログラム実行開始時に勝手に生成される。
  • なのでstatic代わりに使える。

コンパニオンオブジェクトの例

基本型

  • Scalaには次のような型がある
    • 数値型
      • Byte
      • Int
      • Long
      • Short
      • Double
      • Float
      • Char
    • String
    • Boolean

基本リテラル

整数リテラル

  • Int, Long, Short, Byte型のリテラル。
  • 10進、8進、16進表記可能。

浮動小数点リテラル

  • 10進表記のみ。
  • 指数表記もできる。

文字リテラル

  • シングルクォートでUnicode文字を囲んだもの。

文字列リテラル

  • 文字列をダブルクォートで囲ったもの。
  • 改行するとそれもそのまま文字列として反映される

シンボルリテラル

  • シンボルもやはり文字列を表すためのリテラル
  • 次のように表記する

   1 `hoge //hogeのシンボルリテラル
   2 
  • 文字列リテラルとの違いは文字列へのシンボルリテラルであれば必ず同じオブジェクトへの参照になっているということ

   1 val hoge = "hoge"
   2 val hoge2 = "hoge" //hogeとhoge2は違うオブジェクトになっている
   3 
   4 val fuga = `fuga
   5 val fuga2 = `fuga //fugaとfuga2は同じオブジェクト
   6 

Booleanリテラル

  • TrueとFalseがある

演算子

  • 演算子はメソッドです

   1 1 + 2
   2 (1).+(2)  // この2つは同じ意味
   3 
  • 演算子の種類はJavaに準ずるので適当にググってね

比較演算子について

  • 1つだけ注意を要するのが比較演算子の==
  • Javaではオブジェクトとしての等価性を判定するものだった

   1 "hoge" == "hoge" //左と右の文字列は同じオブジェクトかも知れないし違うオブジェクトかもしれないから、TrueかもしれないしFalseかもしれない
   2 
  • Scalaでは内部的にJavaのequalsメソッドを呼び出しているので、==はオブジェクトの持っている値そのものの比較になる

   1 "hoge" == "hoge" //かならずTrue
   2 

演算子の優先順位について

  • Scalaでは演算子の優先順位は演算子の名前で決まっている
    • 演算子は特別な存在ではなく、ただのメソッドだから
    • 例えば*メソッドは+メソッドよりも優先順位が高いとかいう決め方をしている

リッチラッパー

  • ScalaではJavaの演算子に加えてもっと賢い演算子が使えるようになっている
  • 例えば

   1 0 min 5 // min演算子で計算されて結果が0になる
   2 
  • これはリッチラッパーというクラスによって用意されている。ふだん使うぶんにはリッチラッパーという存在を意識せずにそのまま使うことが出来る。

制御構造

if式

  • if文じゃなくてif「式」です
    • 値を返すから

   1 if (条件式) {
   2   //真の時の式
   3 }else{
   4   //偽の時の式
   5 }
  • 条件式が真のときは「真の時の式」の結果が返ってくる
  • 条件式が偽のときは「偽の時の式」の結果が返ってくる
  • 関数定義の時と同じように式が複数あるときは最後の式の結果が返ってくる
  • なので次のように書ける

   1 val hoge = "hoge"
   2 println(if (!hoge.isempty) hoge else "default")

while式, do-while式

  • CとかJavaと同じ。
  • こいつらはUnit型を返す

   1 var a = x
   2 var b = y
   3 while(a != 0){
   4   val temp = a
   5   a = b % a
   6   b = temp
   7 }
  • 一応言っておくとwhileを使うような構文はたいていvarを伴うのでうんこ
  • 使わないようにしましょう
  • 再帰を使うのが常道
    • というわけで練習がてら再帰で上のプログラムを書き換えてみましょう

for式

  • for式を使いこなすのがScalaマスターへの第一歩です
  • for「式」なのでやっぱり値を返す
    • forが値を返すとはどういうことなのかは下のyieldに関する所を読んでね

for式の基本的な使い方

  • コレクションの反復操作
    • コレクションというのは配列とかListとかそのあたり

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 for(member <- yuruyuri){
   3   println(member)
   4 }
  • 「member <- yuruyuri」の部分をジェネレータと呼ぶ

    • memberはvalになっていて、ループが1巡するごとにyuruyuriから1つ要素を取り出してmemberという変数を新たに初期化しているイメージ
    • memberという変数を生成しているのでジェネレータ

Range

  • 順番に並んでいる数値に対して処理をしたいとき

   1 for(i <- 1 to 10) println(i) //(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)という数列が生成される
   2 for(i <- 1 until 10) println(i) //(1, 2, 3, 4, 5, 6, 7, 8, 9)という数列が生成される
   3 

フィルタリング

  • for式では取り出す要素に対してif式で条件を追加できる
  • ifはセミコロンで区切って何個でも並べられる

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 for(member <- yuruyuri if member.endsWith("i")){
   3   println(member)
   4 }

入れ子のfor式

  • <-を複数書くことで入れ子のfor式を1つのfor式でまとめて書くことができる。

   1 for(i <- 1 to 9; j <- 1 to 9) println(i*j)

yield

  • yieldはfor式の中に記述して使う。
  • 下の例の場合for文が実行されるごとにyield i+1というのが実行される。これが実行されるとi+1の結果がfor文の返り値として返ってくるようになる。for文は繰り返し実行されるのでyieldによって返ってきた戻り値は蓄積されていって新しいコレクションになる。つまりこの結果hogeには(1, 2, 3, 4, 5, 6)という配列が格納されることになる。

   1 val hoge = for (i <- 0 to 5) yield i+1
  • yieldの後ろで関数を呼び出すこともできるし、ブロックを書くこともできる。

try-catch-finally

  • ScalaではJavaと違って例外をかならずcatchしなくても良い
  • 次のように書きます

   1 try{
   2   //例外が発生しそうな処理を書く
   3 }catch{
   4     case e: 例外名  => 例外処理
   5 }
  • catch節での例外名の記述を端折るとすべての例外をcatchする式になります
  • caseは複数並べて複数の例外に対応出来ます
  • tryもcatchもブロック式なので、最後の式の結果が全体の結果として返る
    • 例外が発生しなかったときはtry節の最後の式の結果
    • 例外が発生したときは対応する例外の処理の最後の式の結果
  • finally節には例外の有無にかかわらず行いたい処理を書く
    • finally節に値を返すような処理を書くべきではない
      • tryやcatchで返された値を上書きしてしまう可能性があるから
      • 例外の有無にかかわらず行いたい副作用を伴う処理に限って書くべき

match式

  • こいつも強力な式なのだが、とりあえず最も基本的な使い方であるswitch文の代わりとしての使用法を見てみる

   1 val yuruyuri = "kyoko"
   2 yuruyuri match {
   3   case "kyoko" => println("so cute")
   4   case "akari" => println("who is she?")
   5   case _ => println("so so") // _ はdefaultに相当
   6 }
  • match式は式なので次のように書くこともできる
    • 副作用を伴う処理をmatch式の外に出してしまえるのでこっちのほうがいいかも

   1 val yuruyuri = "kyoko"
   2 val result = yuruyuri match {
   3   case "kyoko" => "so cute"
   4   case "akari" => "who is she?"
   5   case _ => "so so"
   6 }
   7 println(result)

ローカル関数

  • Scalaでは関数の中に関数を定義することができる→ローカル関数

   1 def calc(a:Int, b:Int) = {
   2   def add() = a + b
   3   def sub() = a - b
   4   (add, sub)
   5 }
  • 上のaddとsubという関数はcalc関数の中でしか使用できない
  • ローカル関数からローカル関数の外の変数にアクセスすることは可能

関数リテラル

  • 「Scalaらしいループ」というところでforeachメソッドを用いた例を示した
    • そこで以下の様な表現があった

   1 arg => println(arg)
  • これはargという引数をとって、println(arg)という処理を実行する関数になっている
  • より関数型っぽく言えばargという引数に対してprintln(arg)というものを返す関数である
  • Scalaではこのように名前の無い関数をその場で生成して使うことができる。これを関数リテラルとか無名関数などと呼ぶ

第一級オブジェクトとしての関数リテラル

  • 第一級オブジェクトなどというと仰々しいが、要するに関数リテラルは文字列や数値と同じようにScalaでは扱われていますよということ
  • 同じように扱われているので変数に代入できる

   1 val hoge = (x:Int) => x+1
   2 println(hoge(10))

プレースホルダー

  • ある引数が関数リテラルの中で一度しか使われないときは名前を書かずアンダーバーで代用することができる。

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 yuruyuri.foreach(println(_))
   3 // yuruyuri.foreach(x => println(x))と同じ
   4 
  • 型を推論できないような書き方をすると怒られる

   1 val myadd = _ + _ //エラーが出る
   2 
  • 型を明示すれば良い

   1 val myadd = (_:Int) + (_:Int)
   2 println(myadd(2, 3))
  • アンダーバーで置き換えられるのは1回だけなので、アンダーバーの個数だけ引数があるとみなされる
    • だからアンダーバーが例えば2つあれば2引数の関数なのだ、という認識をコンパイラはするわけ

部分適用

  • アンダーバーによる置き換えは関数の引数リストに対しても適用出来る。

   1 def sum(a:Int, b:Int, c:Int) = a + b + c
   2 val sum2 = sum(_:Int, 3, _:Int)
  • 上の操作によってsum関数の第2引数に3を与えた引数を2つとる関数を新しく作ることが出来た
    • このように関数に引数の一部分だけを適用して新しい関数を得ることを部分適用という
  • 上の操作をした上で下のような操作をすることができる

   1 println(sum2(2, 4)) //9が出力される
   2 
  • 部分適用とは言うものの1つも引数を適用しないこともできる

   1 def sum(a:Int, b:Int, c:Int) = a + b + c
   2 val sum2 = sum _
  • 上の操作の結果sum2にはsumと同じ挙動をする3引数の関数が格納されている状態になる

部分適用の内部的動作

  • 次のコードを考える

   1 def sum(a:Int, b:Int) = a + b
   2 val sum2 = sum _
  • sum2にはsumに引数を0個部分適用した結果の2引数関数のオブジェクトが格納されている
  • sum2に格納されているものは実は厳密にはsumで定義されている関数そのものではない

   1 sum2(2, 3)
  • 上の式を評価するとsum2オブジェクトが持っているapplyというメソッドが呼び出され、sum2に対応する関数、すなわちsum関数が取り出され適用されるという形になっている

アンダースコアの省略

  • 「その関数式を記述する箇所が関数呼び出し必須である」ようなときアンダースコアを省略することも可能である

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 yuruyuri.foreach(println)
   3 // foreachメソッドの引数には呼び出す関数を必ず記述しなければならないため、println(_)のアンダースコアは省略できる
   4 

クロージャ

束縛変数と自由変数

高階関数

カリー化

名前渡しパラメータ

alstamber/Scala (最終更新日時 2013-03-09 01:51:21 更新者 alstamber)