第二回scala勉強会に行ってきました。
しかも発表してきました。
発表ていうか、REPLいじって遊んだだけですが、スライドとか事前に作らずに、よってustreamでも中継できなかったり、更には遅刻するなどいろいろ迷惑かけてすいません(−_−;)
なので、ちゃんとまとめようと思います。
発表の題材
なんか普通に発表しても面白くないなぁーと思い、何をしようか考える。
↓
初心者向けらしいので、やっぱり実際に動かして見せたほうがいいかな?
↓
なにを動かす?
↓
clojure本に、短いJavaの関数を、clojureに書き換えた例が載っていたので、じゃあこれをScalaへ書き換えてみようか
という感じでなんとなく自分の中で決定!
ネタとなるJavaコード
clojure本2ページより引用
Apache Commonsにあるらしいです。機能としては、引数がnullまたは空文字または、すべての文字が空白文字の場合はtrueをかえす。
空白文字というか、isWhitespaceという関数についてはここ読んでね。
あからさまに、手続き型のコードを探してたんじゃないかと思われるような、微妙なコード。
public class StringUtils { public static boolean isBlank(String str) { int strLen; if( str == null || ( strLen = str.length() ) == 0 ) { return true; } for( int i = 0; i < strLen ; i++ ) { if( ( Character.isWhitespace(str.charAt(i)) == false )) { return false; } } return true; } }
clojureだとこうなるよ
(defn blank? [s] (every? #(Character/isWhitespace %) s))
この時点で、clojureとか関数系の言語わかる人があまりいないという予想だったが、予想が外れ、なんだか自分の発表初心者向け過ぎるんじゃないかって気がしてきたが、まぁしょうがない。
で、これをScalaに少しずつ置き換えて、Scalaコードにしてみる。
- public ってキーワードはないよ(デフォルトがpublicなので。ちなみに、Javaはpublicやprivate付けないと、package privateという一見わかりづらいスコープになる)
- 関数や変数の型は、後ろに書く。
- 関数の最初はdef(このへんはrubyとかと似たような感じ)
- 変数の前には、再代入不可能な変数にするならval。再代入可能ならvar(Javaでfinal付けるかつけないかと同じようなもの)
- セミコロンつけなくてもいい
- staticもないよ。とりあえずJava分かる人は、static関数は、classの代わりにobjectって宣言してその中に書くと覚えましょう。
scalaだとこうなる
object Hoge{
def fuga:Int = 3
}
で、その辺を適用するととこうなる(この状態だとJavaとしても、scalaとしてもコンパイルできません。それと、nullチェックの問題とかあったので、このへんから若干発表のときのものと違ってます)
object StringUtils{ def isBlank(str:String):Boolean = { var strLen = 0 if( str == null || { strLen = str.length(); strLen } == 0 ) { return true } for(var i:Int = 0; i < strLen ; i++ ){ if( (Character.isWhitespace( str.charAt(i) ) == false )){ return false } } return true } }
しかし、このままだと、scalaにはforループがないので、エラーになる。
この、 for( 初期化処理 ; ループ継続条件 ; ループ一回ごとに行う処理 ) というC言語由来のループはscalaにはありません。
とりあえず置き換えるとしたら。whileにするしかない。それと、i++っていう形式のインクリメントもできない。
object StringUtils{ def isBlank(str:String):Boolean = { var strLen = 0 if( str == null || { strLen = str.length(); strLen } == 0 ) { return true } var i:Int = 0; while( i < strLen ){ if( (Character.isWhitespace( str.charAt(i) ) == false )){ return false } i += 1 } return true } }
この状態で見事scalaプログラムとしてコンパイルとおる(´∀`)/
で、ここからscalaらしくしていく。
まずはreturnが3箇所もあるので、 if式 使って、1箇所にしてみる。
( 時間なくてこのあたり、発表ではかなり省略してしまった。あと、めんどくさいから、object省略して書く。でも、無理やりreturnなくしてフラグつくっても、かえって複雑か・・・orz
def isBlank(str:String):Boolean = { var strLen:Int = 0 if( str == null || { strLen = str.length(); strLen } == 0 ) { true } else { var i:Int = 0; var flg: Boolean = true while( ( i < strLen ) && flg ){ if( (Character.isWhitespace( str.charAt(i) ) == false )){ flg = false } i += 1 } flg } }
で、まぁこのへんで、高階関数登場させましょう。while文、ループ用の変数のiがいらなくなります。そうすると、strLenっていう変数も、一度しか使わないので、lengthメソッドも直接よんで、strLenの変数消しましょう。
def isBlank(str:String):Boolean = { if( str == null || ( str.length() == 0 ) ) { true }else{ str.forall{ x:Char => Character.isWhitespace(x) } } }
ここで出てきたforallには、"charを引数にとり、Booleanを返す関数" をリテラルとして書いて、渡しています。
まぁこの関数を引数として渡すという概念が、手続き型から関数型へ切り替えるのに、一番苦労するところですが、この考え方がわかれば、関数型言語を勉強するのが楽しくなってきます。
(ちなみに、Javaで同じことやろうとすると、インターフェイス定義して、そのインターフェイスから無名クラス生成なんて、めんどくさいことしなければならないので、結局ループになっちゃいますね。)
そして、そもそもnull渡してくる方がわるい!ってことで、nullチェック省略とかして、実は空文字の処理もいらないので、するとこうなります
def isBlank(str:String):Boolean = { str.forall{ x:Char => Character.isWhitespace(x) } }
さらにさらに、関数リテラルの引数が一度しか使われない場合、省略できる。あと、ひとつの式になってるから、中括弧も省略できる
def isBlank(str:String):Boolean = str.forall{ Character.isWhitespace }
あと、戻り値の型Booleanも省略できるんですが、ここ省略するのはやりすぎだと思うので、普通やらないと思います( 関数使う人からわかりにくいので。関数の中のローカル関数とかだったらありかなぁ?)
あとは、1度しか使わなく、かつこんなに短い関数なら、そもそもisBlankなんて名前つけないで、その場で生成しちゃって無名関数って言う方法も自分的にはありだと思います。
まぁ自分の発表はとりあえずこんな感じで。
他にもいろいろ話題が出たんですが(Null型ってなに?とか、最後がコロンで終わると結合順変わるとか、その場でscalaコードコンパイルして、逆コンパイルしてみるとどうなるかとか・・・)
とりあえず、あとで時間あれば書きます。