この前のエントリの続き
エラー修正して、とりあえず実行できるようになったところで、まずは・・・classとobjectに分かれてるけど、この場合objectだけでいいんじゃね?って思ったので*1objectだけにしちゃいました
object Reflexa { val url = "http://labs.preferred.jp/reflexa/api.php" /* * reflexa apiへアクセス */ def getXMLSource(query: String) = { val src = Source.fromURL(url + "?q=" + query + "&format=xml","UTF-8") println() XML.loadString(src.mkString) } /* * 検索結果のxmlをパースし、結果の配列を作成 */ def makeWordList(query: String) = { val xml = getXMLSource(query) val words = xml \\ "word" words size match { case 0 => Array() case _ => words.map(word => word.text).toArray } } /* * 検索文字列からqueryを作成 * 文字列同士は「%20」で連結 */ def makeQuery(search_words: Array[String]) = { search_words.map(URLEncoder.encode).mkString("%20") } def search(search_words: Array[String]) = { val query = makeQuery(search_words) makeWordList(query) } def main(arg: Array[String]):Unit = { val line = Console.readLine(">>> ") val split_reg = """[\s ]""".r val list = search(split_reg.split(line)) list.map(println) } }
classにするかどうかっていうのは、自分の判断基準としては、
という理由でしょうか?*2
逆に言えば、純粋な関数だけしかない場合は、objectでいいってことになります。
次に色々細かいところを"自分だったらこう書く"っていう判断基準で書き換えてみます。
/** * あえて、全部戻り値の型かいてみた */ object Reflexa { val url = "http://labs.preferred.jp/reflexa/api.php" /* * reflexa apiへアクセス */ def getXMLSource(query: String):Elem = { //ゆろよろさんのblogにも書いてあったけど、ホントはSourceって閉じないといけない・・・ val src = Source.fromURL(url + "?q=" + query + "&format=xml","UTF-8") println() val result = XML.loadString(src.mkString) src.close result } /* * 検索結果のxmlをパースし、結果の配列を作成 */ def makeWordList(query: String):Array[String] = { val xml = getXMLSource(query) val words = xml \\ "word" words.map(_.text).toArray //0の場合の判断いらないはず。mapの引数の名前省略できる } /* * 検索文字列からqueryを作成 * 文字列同士は「%20」で連結 */ def makeQuery(search_words: Array[String]):String = { search_words.map(URLEncoder.encode).mkString("%20") } def search(search_words: Array[String]):Array[String] = { val query = makeQuery(search_words) makeWordList(query) } def main(arg: Array[String]):Unit = { val line = Console.readLine(">>> ") val split_reg = """[\s ]""".r val list = search(split_reg.split(line)) list.foreach(println) //値を返さない(かえされたもの使わない)ので、mapではなくforeachを使うべき } }
つぎは・・・勉強会でも話でたんですが、ArrayかListどっち使うかって言う話。
違いはかなり色々あって、時間あれば別エントリで書いてみますが、
個人的には、scalaや関数型になれるためには、基本的に何も考えずにList使ったほうがいいと思います。
サーバーサイドでずっと動き続けるような、重要なプログラムかくなら別ですが、小さいプログラムなら、パフォーマンスなんて考えても殆ど変わりません。
むしろ関数型に慣れるためには、Arrayじゃないとかけなそう(つまり値を途中で変更する必要がありそう)な場合でも、Listでかくにはどうすればいいんだろう?っていう姿勢でいたほうがいいです*3
ってことでListに書き換えます。この場合は何も考えずに書き換えればいいだけで、とくに難しくありませんが。
その他細かいとこ変更してみる
import scala.io.Source import scala.xml.{XML,Elem}//まとめて1行に書くことできる import java.net.URLEncoder //Listはインポートする必要ない(Predefでimportされてる) object Reflexa{ val url = "http://labs.preferred.jp/reflexa/api.php" /** * @param query エンコード済みの、検索する文字列 * @return 検索結果のxml */ def getXMLSource(query: String):Elem = { val src = Source.fromURL(url + "?q=" + query + "&format=xml","UTF-8") val result = XML.loadString(src.mkString) //ここにprintlnあるのは、おかしい気がするので移動 src.close result } /** * @param query 検索用にエンコード済みの、検索する文字列 * @return wordのタグの中身のList */ def makeWordList(query: String):List[String] = { val xml = getXMLSource(query) val words = xml \\ "word" words.map(_.text).toList } /** 検索文字列からqueryを作成 * @param 検索する文字列 * @return それぞれの文字列をエンコードして、文字列同士を「%20」で連結したもの */ def makeQuery(searchWords: List[String]):String = searchWords.map(URLEncoder.encode).mkString("%20") //関数自体が1つの式の場合中括弧省略できる(省略すべきかどうかの判断基準はよくわからんが、標準ライブラリはけっこう省略してると思う) /** * @param searchWords 検索する文字列のList * @return 検索結果の文字列のList */ def search(searchWords: List[String]):List[String] = { val query = makeQuery(searchWords)//scalaでは変数名で単語と単語つなげるときはアンダースコア使わない慣習 makeWordList(query) } def main(arg: Array[String]):Unit = { val line = Console.readLine(">>> ") val list = search(line.split(" ").toList)//この場合だったら正規表現使わなくても、これだけでもできる println//printlnはこっちにもってきた list.foreach(println) } }
ここで気づいたのが、
"URLのエンコードって、空白を%20に変換するんじゃなかったっけ?だったら、コンソールからの入力まとめて全部エンコードすればよくね?"
っていうこと。
拡張性とか、入力文字列の検査とか考えると、むしろ今のままのほうがいい気がしますが、"こういう風にも書けるよ"って例ってことで、あえて書き換えてみます。
val url = "http://labs.preferred.jp/reflexa/api.php" /** * @param query エンコード済みの、検索する文字列 * @return 検索結果のxml */ def getXMLSource(query: String):Elem = { val src = Source.fromURL(url + "?q=" + query + "&format=xml","UTF-8") val result = XML.loadString(src.mkString) src.close result } /** * @param query 検索用にエンコード済みの、検索する文字列 * @return wordのタグの中身のList */ def makeWordList(query: String):List[String] = { val xml = getXMLSource(query) val words = xml \\ "word" words.map(_.text).toList } /** * @param searchWords 検索する文字列 * @return 検索結果の文字列のList */ def search(searchWords: String):List[String] = makeWordList( URLEncoder.encode(searchWords) ) def main(arg: Array[String]):Unit = { val line = Console.readLine(">>> ") val list = search(line) println list.foreach(println) }
こんな感じでしょうか。
やってみて思ったけど、このぐらい短いと、(細かいところは、色々違う書き方をすることもできますが)明らかに書き換えが必要そうなところってないですね。
プログラム拡張していくんだったら、
getXMLSourceっていうのが、URL全体を渡して、結果をxmlで返すようにしたほうが汎用性がある気もしますが、
このぐらいの短いコードなら、そんなことより直接埋め込んだほうが、分かりやすい気もしますし。
あと付け加えるなら、これはscalaに限らないと思いますが、コメントについて、
"関数の中の処理がどうなっているか?"
を書くのではなく、
"なにを渡すと、なにを返してくれるか?"
を書いたほうがいいと思います。
javaやってる人にはおなじみですが、
そのために、scaladoc形式で最低限 @param と @return をかくといいかと思います。
あとは、privateのものはコメント省略しても、publicのものはちゃんとかくとか
*4