Page List

Search on the blog

ラベル Scala の投稿を表示しています。 すべての投稿を表示
ラベル Scala の投稿を表示しています。 すべての投稿を表示

2019年6月22日土曜日

Scala: implicit の使い方

Scala の implicit の使い所をまとめておく。

Pimp My Library

標準機能やサードパーティライブラリのクラスを拡張したいときに使う。

object ImplicitInt {
  implicit class RichInt(val x: Int) {
    def negate: Int = -x
    def square: Int = x * x
  }
}

上記のような implicit class を作っておいてimport すると、Int型の変数で squareメソッド、negate メソッドが使えるようになる。

import ImplicitInt._

object Main {
  def main(args: Array[String]): Unit = {
    // pimp my library
    val x = 100
    println(x.square)  // 10000
    println(x.negate)  // -100
  }
}

Implicit Conversion

暗黙の型変換。

case class Rational(real: Double, imag: Double) {
  def +(that: Rational) = Rational(real + that.real, imag + that.imag)
}

object Rational {
  implicit def double2Rational(x: Double): Rational = new Rational(x, 0)
}

上記のように複素数を扱うクラスを定義したとする。複素数と実数の足し算をしたい場合、implicit conversion を使うと以下のように書ける。わざわざ Double から Rational クラスを明示的につくらなくていいので便利。


object Main {
  def main(args: Array[String]): Unit = {
    val x = Rational(1.0, 1.0)
    val y = x + 2.0
    println(y)  // Rational(3.0,1.0)
  }
}

Implicit Parameter

暗黙的なパラメータ。実行コンテキストなどのパラメータを暗黙的にメソッドに渡してくれる。

import scala.concurrent.{ExecutionContext, Future}

class RemoteCall {
  def get(x: Int)(implicit ex: ExecutionContext): Future[Int] = Future {
    println("heavy remote call...")
    Thread.sleep(5000)
    x
  }
}

上記のような重い処理があって、メインスレッドとは別のスレッドで実行したいとする。 ExecutionContext を implicit で受け取れるように定義されているため、以下のように実行コンテキストを渡すことができる。

import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService}
import scala.util.{Failure, Success}

object Main {
  def main(args: Array[String]): Unit = {
    implicit val ex: ExecutionContextExecutorService =
      ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())

    val remoteCall = new RemoteCall
    remoteCall.get(123) onComplete {
      case Success(r) =>
        println(r)
        ex.shutdown()
      case Failure(t) => println(t.getMessage)
    }
    println("do something else")
  }
}

他にもデータベース接続をするときに、接続まわりを担当するクラスを implicit parameter で渡すなどの使い方がある。

2018年1月17日水曜日

Scalaのapply、unapplyについて

applyの説明


class Human(val name: String, val age: Int) {
}

object Human {
  def apply(name: String, age:Int) = {
    new Human(name, age)
  }
}

としておくと、以下のようにインスタンスを生成できる。

val h = Human("taro", 20)

コンパニオンオブジェクトのapplyがファクトリメソッドとして使われるので、newと書かなくてもインスタンスを生成できる。

unapplyの説明


class Human(val name: String, val age: Int) {
}

object Human {
  def unapply(h: Human): Option[(String, Int)] = Some((h.name, h.age))
}

としておくと、以下のようにパターンマッチで使える。

val h = new Human("taro", 20)
h match {
  case Human(name, age) => println(s"name=$name, age=$age")
  case _ => println("unknown")
}


ケースクラス

case classを使ってあんなことやこんなことができるのは、実はapply、unapplyが自動生成されていたかららしい。

case class Human(name: String, age: Int) {
}

val h = Human("taro", 20)
h match {
  case Human(name, age) => println(s"name=$name, age=$age")
  case _ => println("unknown")
}

2017年9月17日日曜日

expressionとstatementの違い

まえがき

Scala Schoolを読んでいて、以下のような表現に遭遇した。

Scala is highly expression-oriented: most things are expressions rather than statements.

日本語ではexpressionは「式」、statementは「文」と訳される。

両者の違い

プログラムの例を見ると言わんとすることは分かるけど、両者の定義が明確に分からなかったので調べてみた。
  • 式は値を生み出す、文は何か処理をする
  • 式は文の部分集合
  • 式は”それは何か”を表しており、文は"何をするか"を表している
  • 関数型言語は式を、命令型言語は文を使うことが多い気がする

Pythonでの例

if/else
以下のようにif/elseはstatementっぽくも書けるし、expressionっぽくも書ける。

# statement
x = 1
if x % 2 == 0:
 y = "even"
else:
 y = "odd"

# expression
"even" if x % 2 == 0 else "odd"

print
printはPython 2までは文だったが、Python3から式になった。

Python 2では文なので値は返されない。

>>> type (print ("hello"))
  File "<stdin>", line 1
    type (print ("hello"))
              ^
SyntaxError: invalid syntax

Python 3では式なので値が返される。

>>> type (print ("hello"))
hello
<class 'NoneType'>

2016年6月14日火曜日

scalaでneologd版kuromojiを使う

依存ライブラリ
scala-sample/build.sbt

以下の2行が対象。
resolvers += "CodeLibs Repository" at "http://maven.codelibs.org/"
"org.codelibs" % "lucene-analyzers-kuromoji-ipadic-neologd" % "6.0.0-20160519"

lucene-analyzers-kuromoji-ipadic-neologdのバージョンは公式レポジトリを見て最新のものを選ぶといい。

サンプルコード
ぱるる null 名詞-固有名詞-一般
と null 助詞-並立助詞
こじはる null 名詞-固有名詞-一般
と null 助詞-並立助詞
さや姉 null 名詞-固有名詞-一般
と null 助詞-並立助詞
ゆきりん null 名詞-固有名詞-一般
仕事で使うときは製品名や地名、店名などをうまく分かち書きしてくれるので重宝するはず。
ちなみにデフォルト辞書版kuromojiを使うと、以下のように綺麗に分かち書きできない。
ぱるるとこじはるとさや 名詞,一般,*,*
姉 名詞,一般,*,* 姉
と 助詞,並立助詞,*,*
ゆき 名詞,一般,*,*
りん 副詞,助詞類接続,*,*

2016年6月12日日曜日

SparkとHadoop MapReduceの違い

-- Apache Hadoop logo and Spark log[1, 2] --

比較まとめ
Hadoop MapReduce
Spark
速度
高速 MapReduceの10-100倍高速
データ
ディスクに保存
ディスクIOに多くの時間を必要とし、レイテンシが大きい
メモリに保存
レイテンシが小さい
Real-Time分析
バッチ処理用に設計されているため、得意ではない ストリーミングデータの分散処理をサポート
Iterative Algorithm
iterationごとに、ディスクからの入力読込、ディスクへの出力書込が必要なため不向き 中間結果をキャッシュし、キャッシュに対して複数のiterationを走らせるため高速
Graph Algorithm
隣接ノードの情報をメッセージングする機構が備わっていない GraphXというグラフアルゴリズムライブラリが含まれている

速度
MapReduceはHadoopクラスタのメモリを有効活用できていなかった。
SparkではRDD(Resilient Distributed Datasets)を使うことで、データをメモリに保存することができ、必要な場合にのみディスクへの保存を行うことができる。
これにより、SparkはHadoopよりも格段に高速である。

データ
Hadoopはデータをディスクに保存するが、Sparkはメモリに保存する。
SparkはRDD(Resilient Distributed Datasets)とよばれるデータストレージモデルを用いる。RDDはnetwork IOを最小化するフォールトトレランスの機構を提供する。RDDの一部のデータが失われた場合、lineage(データに提供された処理の履歴)を元に再構築が行われる。このためフォールトトレランスのためのレプリケーションが不要となる。
これに対して、Hadoopはフォールトトレランスのためのレプリケーションを必要とする。

Real-timeデータ分析
Twitterのデータを分析する場合などは、毎秒数百万単位で発生するイベントを処理する必要がある。Sparkの利点の一つは、データストリーミングの分散処理をサポートしている点である。標準で提供されるSpark Streamingライブラリを利用することで、バッチジョブを書く場合と同じ方法でストリーミングジョブを書くことができる。
これに対してMapReduceはバッチ分散処理用にデザインされているため、Real-time分析が不得意である。

Iterative Algorithm
多くのデータ分析アルゴリズムはiterative algorithmとよばれる繰り返し処理を必要とする。例えば、k-means、LDA、PageRankなどがその例である。
Hadoopの場合、各iterationでの計算結果をディスクに書き込み、次のiterationで結果をディスクから読み込むという処理が必要なため、iterative algorithmを高速に実行することは困難である。
Sparkではiterationごとの結果をメモリ上に保存しておけるため、高速に計算することができる。またSparkではMLlibというMachine Learning系の処理を行うためのライブラリが標準で提供されている。

Graph Algorithm
グラフ構造のデータに提供するアルゴリズムの多くでは、隣接するノードの情報が必要となる。例えば、PageRankの場合は、自身のノードにリンクを張っているノードのPageRank値が計算に必要になる。
Hadoopの場合、隣接ノードの情報をメッセージ するための機能は提供されていない。これに対してSparkではGraphXという標準ライブラリを使うことで、グラフ系のアルゴリズムを効率的に計算することができる。Sparkは、NettyとAkkaのコンビネーションを使ってメッセージの配信を行っている。


参考URL
[1] Apache Hadoop logo, Apache Software Foundation - https://svn.apache.org/repos/asf/hadoop/logos/out_rgb/, Apache License 2.0
[2] Spark Logo, Spark project team - Spark open source project - UC Berkeley, Apache License 2.0
[3] Apache Spark vs Hadoop MapReduce
[4] What is the difference between Apache Spark and Apache Hadoop (Map-Reduce) ? - Quora

2016年6月6日月曜日

SparkのCountVectorizerを使ってみた

NLP系の前処理としてBow行列を作りたい場合、CountVectorizerが便利です。

サンプル
package com.kenjih

import org.apache.spark.{ SparkConf, SparkContext }
import org.apache.spark.ml.feature.CountVectorizer
import org.apache.spark.sql.SQLContext
import org.apache.spark.ml.feature.{ CountVectorizer, CountVectorizerModel }

object CountVectorizerSample {

  def run(sc: SparkContext): Unit = {
    val sqlContext = new SQLContext(sc)
    import sqlContext.implicits._

    val path = "data/sample1.txt"
    val rdd = sc.textFile(path).map { line =>
      val ws = line.split("\t")
      val textId = ws(0)
      val words = ws(1).split(" ")
      (textId, words)
    }
    val df = rdd.toDF("id", "text")

    val cvm: CountVectorizerModel = new CountVectorizer()
      .setInputCol("text")
      .setOutputCol("features")
      .setMinDF(2)
      .fit(df)

    cvm.vocabulary.zipWithIndex.map(_.swap).foreach(println)
    cvm.transform(df).select("features").show()
  }

  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Simple Application")
    val sc = new SparkContext(conf)
    run(sc)
  }
}

実行結果
kenjih$ cat data/sample1.txt 
0 hello hello hello
1 hello world
2 goodbye world
3 I love you
4 you love me% 
kenjih$ 
kenjih$ spark-submit --master local --class com.kenjih.CountVectorizerSample target/scala-2.10/spark-sample.jar
(0,hello)
(1,you)
(2,love)
(3,world)
+-------------------+
|           features|
+-------------------+
|      (4,[0],[3.0])|
|(4,[0,3],[1.0,1.0])|
|      (4,[3],[1.0])|
|(4,[1,2],[1.0,1.0])|
|(4,[1,2],[1.0,1.0])|
+-------------------+

説明
  • CountVectorizerを適用するcolumnはArray[String]にしておく。
  • setMinDFでDocument Frequencyの最小値を設定できる。(最小値未満のドキュメントにしか単語はcorpusに含めない)
  • CountVectorizerModel.vocabularyでcorpusの単語を参照できる。zipWithIndexしておくと、確認のときに便利。 

2016年6月4日土曜日

Sparkのテンプレートプロジェクトを作る

giter8のtemplatesのリストを参照すると、Sparkのテンプレートがあるようだ。
これを使うと、Sparkのsbtプロジェクトを簡単に作成することができる。

テンプレート生成
$ g8 nttdata-oss/basic-spark-project.g8

A basic spark application project 

name [Basic Spark]: spark-sample
package [com.example]: com.kenjih
version [0.0.1]: 0.0.1
context [anonymous] 3:18 attribute organization isn't defined
context [anonymous] 25:28 attribute organization isn't defined

Template applied in ./spark-sample

生成されたファイルの確認
$ find spark-sample 
spark-sample
spark-sample/assembly.sbt
spark-sample/build.sbt
spark-sample/project
spark-sample/project/assembly.sbt
spark-sample/project/plugins.sbt
spark-sample/README.rst
spark-sample/src
spark-sample/src/main
spark-sample/src/main/scala
spark-sample/src/main/scala/com
spark-sample/src/main/scala/com/kenjih
spark-sample/src/main/scala/com/kenjih/GroupByTest.scala
spark-sample/src/main/scala/com/kenjih/RandomTextWriter.scala
spark-sample/src/main/scala/com/kenjih/SparkHdfsLR.scala
spark-sample/src/main/scala/com/kenjih/SparkLR.scala
spark-sample/src/main/scala/com/kenjih/SparkLRTestDataGenerator.scala
spark-sample/src/main/scala/com/kenjih/SparkPi.scala
spark-sample/src/main/scala/com/kenjih/WordCount.scala
spark-sample/src/main/scala/com/kenjih/Words.scala
spark-sample/src/test
spark-sample/src/test/scala
spark-sample/src/test/scala/com
spark-sample/src/test/scala/com/kenjih
spark-sample/src/test/scala/com/kenjih/SparkPiSpec.scala

設定ファイルの中身を見ておく。
PROJECT_DIR/project/plugins.sbt
sbtプラグインの設定
 - eclipse
 - idea

PROJECT_DIR/project/assembly.sbt
sbtプラグインの設定
 - sbt-assembly

※  sbt-assemblyはプロジェクトと依存ライブラリをまとめてJARにする機能を提供するプラグイン。

PROJECT_DIR/build.sbt
sbtのビルド設定。
 - spark
 - hadoop

PROJECT_DIR/assembly.sbt
sbt-assemblyの定義。jarのファイル名とか。

このファイルの中には以下の記述がある。
run in Compile <<= Defaults.runTask(fullClasspath in Compile, mainClass in (Compile, run), runner in (Compile, run))

これは、"provided"スコープのライブラリもjarに含めるという設定。
これを書かないと"provided"スコープのライブラリはjarファイルには含まれない。
"provided"スコープは、コンテナ(Sparkなど)側で提供されるライブラリなのでアセンブルされるjarからは除外するという意味。
jarファイルはspark-submitするときに使うだけだという場合は、この行は不要なので削除していい気がする。

実行
まずはユニットテストから。
$ sbt test
テストは円周率の計算を乱択で行うもので稀に失敗するので注意。  

次にJarにアセンブルして、spark上で実行してみる。
sbt assembly
PROJECT_DIR/target/scala-2.10/spark-sample.jarが生成される。

このjarをspark-submitして実行する。
spark-submit --master local --class com.kenjih.SparkPi target/scala-2.10/spark-sample.jar
pi: 3.146のように計算された円周率が標準出力に表示される。

sbtプロジェクトをEclipseにインポート

sbteclipseを使うとsbtプロジェクトをEclipseにインポートできる。

sbteclipseとは?
Eclipseのプロジェクト定義を生成してくれるsbtのプラグイン。
https://github.com/typesafehub/sbteclipse

プラグインの定義
PROJECT_DIR/project/plugins.sbtに以下を書く。
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")

プラグインの実行
$ sbt eclipse
プロジェクトファイルが生成されていることを確認。
$ ls -la
total 24
drwxr-xr-x  9 kenjih  staff   306  6  4 17:41 .
drwxr-xr-x  4 kenjih  staff   136  6  4 17:27 ..
-rw-r--r--  1 kenjih  staff  1006  6  4 17:41 .classpath
-rw-r--r--  1 kenjih  staff   360  6  4 17:41 .project
drwxr-xr-x  3 kenjih  staff   102  6  4 17:41 .settings
-rw-r--r--  1 kenjih  staff   339  6  4 17:27 build.sbt
drwxr-xr-x  5 kenjih  staff   170  6  4 17:41 project
drwxr-xr-x  4 kenjih  staff   136  6  4 17:27 src
drwxr-xr-x  4 kenjih  staff   136  6  4 17:41 target

Eclipseへのインポート
File> import> Existing Project into Workspaceからインポート。

giter8でScalaプロジェクトのテンプレートを作る

インストール
$ brew install giter8

プロジェクトの作成
$ g8 fayimora/basic-scala-project

name [Basic Project]: sample
organization [com.example]: com.kenjih
version [0.0.1]: 0.0.1

生成されたプロジェクトの確認
$ tree
.
└── sample
    ├── build.sbt
    └── src
        ├── main
        │   └── scala
        │       └── HelloWorld.scala
        └── test
            └── scala
                └── HelloWorldSpec.scala

6 directories, 3 files

プロジェクトの実行
$ sbt run
[info] Set current project to sample (in build file:/Users/kenjih/tmp/scala/g/sample/)
[info] Running com.kenjih.sample.HelloWorld 
Hello World!!!
[success] Total time: 1 s, completed 2016/06/04 17:17:27

2015年10月6日火曜日

[NewLang] Learn Scala(3)

 Scalaの勉強3回目です。
前回チュートリアルサイトを読破しましたので、今回から以下の本を読んでいくことにしました。



最初の方は比較的易しいのでChapter2まで読みました。
練習問題の回答を載せようと思いましたが、それだと芸がないので自分で問題を作って解いてみました。
  1. Int型の配列を受け取り配列内の要素の最大値を返す関数を定義せよ。関数のシグネチャはdef max(x : Array[Int]) : Intとする。
  2. 関数fと非負整数nを受け取り、関数f^n(関数fをn回適用する)を返す関数を定義せよ。関数のシグネチャはdef apply[A](f: A => A, n: Int) : A => Aとする。
以下サンプル回答です。
package chapter2

object Problem1 {
  
  def max(x : Array[Int]) : Int = {
    
    @annotation.tailrec
    def go(i: Int, acc: Int) : Int = {
      if (i == x.length) acc
      else if (acc > x(i)) go(i+1, acc)
      else go(i+1, x(i))
    }
    
    go(1, x(0))
  }
  
  def main(args: Array[String]) : Unit = {
    println(max(Array(2, 3, 1))) 
    println(max(Array(100)))
  }  
}
package chapter2

object Problem2 {
  
  def apply[A](f: A => A, n: Int) : A => A = {
    @annotation.tailrec
    def go(acc: A => A, m: Int) : A => A = {
      if (m == 0) x => acc(x)
      else go(x => f(acc(x)), m-1)
    }
    go(x => x, n)
  }
  
  def main(args: Array[String]) : Unit = {
    val f = apply[Int](_ + 2, 5)
    val g = apply[Int](_ * 2, 10)
        
    println(f(1))
    println(g(10))
    
  }  
}

2015年10月3日土曜日

[NewLang] Learn Scala(2)

  Scalaの勉強2回目です。前回に引き続き、LEARN SCALAR PROGRAMMINGを読みました。

学んだこと
  • クロージャが使える
  • Javaで使えるすべてのクラスを利用できる
  • StringJavaStringと同じ
  • 配列はvar z = new Array[String](3)のように宣言
  • 配列を初期化する場合は、var z = Array(“hoge”, “fuga”, “bar”)のように宣言
  • 多次元配列は、var z = Array.ofDim[int](3,3)
  • collectionsにはstrict/lazyがある
  • collectionsにはmutable/immutableがある
  • 継承できるクラスの個数は1個
  • クラスはstaticメンバーを持てない
  • シングルトンなクラスはobjectキーワードで作成
  • traitはシグネチャを定義する(Javaのabstract classと同様)
  • traitは継承と異なり多重ミックスイン可能
  • パターンマッチングをサポート
  • 正規表現をサポート
  • 正規表現パターンはJavaのものを使用
  • 例外処理はtry/catch/finallyで行い、例外の種類はパターンマッチで識別する
  • extractorはunapplyというメソッドを持つ。
  • extractorはオブジェクトから値を取り出すときに使われ、パターンマッチで使用できる。

2015年9月29日火曜日

[NewLang] Learn Scala(1)

 なんとなく新しい言語を学びたくなったので、Scalaについて勉強していこうと思う。
まずは、tutorialspointというサイトの『LEARN SCALA PROGRAMMING』を読んでみた。今日は半分くらいまで読んだ。

学んだこと
  • 2003年にMartin Odersky氏によって開発された言語
  • オブジェクト指向言語
  • 関数型言語
  • 静的型付け言語
  • JVM上で動作する
  • すべてオブジェクト。Primitive型はない。
  • mutableな変数はvarで宣言
  • immutableな変数はvalで宣言
  • 型推論あり
  • アクセス修飾子はprotected/privateの2つが指定可
  • アクセス修飾子を省略するとpublic
  • イコール記号とメソッドのボディを省略すると、メソッドはabstractになる
  • voidの代わりにUnitを使う
  • methodはクラスに属する処理
  • functionは変数に代入できる完全なオブジェクト
  • 戻り値の無い関数はprocedureと呼ばれる
  • call-by-name(変数: => 型)を使うと遅延評価
  • 無名関数は引数 => 戻り値のように宣言
  • 関数の部分適用をサポート
  • 関数のカリー化ができる

環境インストール
MacにScalaの環境をインストールした。
> brew install scala
> brew cask install scala-ide

サンプルプログラム
FizzBuzz、ユークリッドの互除法、たらい関数を書いてみた。
object FizzBuzz {
  def main(args: Array[String]) {
    var i = 0;
    for (i <- 1 to 100) {
      if (i % 3 == 0 && i % 5 == 0)
        println("FizzBuzz")
      else if (i % 3 == 0)
        println("Fizz")
      else if (i % 5 == 0)
        println("Buzz")
      else
        println(i)
    }
  }
}
object Gcd {
  def main(args: Array[String]) {
    println(gcd(132, 66));
  }
  def gcd(a:Int, b:Int) : Int = {
    if (b == 0)
      return a
    return gcd(b, a%b)
  }
}
object Tarai {
  def main(args: Array[String]) {
    println(tarai(20, 10, 5));
  }
  def tarai(x:Int, y:Int, z: => Int) : Int = {
    if (x <= y)
      return y
    return tarai(tarai(x-1,y,z), tarai(y-1,z,x), tarai(z-1,x,y))
  }
}