Download - Non-Functional Programming in Scala
Non-Functional Programming in Scala
Naoki Takezoe@takezoen
BizReach, Inc
自己紹介
竹添 直樹: @takezoen
ビズリーチという会社でScalaを書いてます
OSS開発や技術書の執筆などもしています
主題
Scalaがもっと普及して欲しい
そうしないと自分が失業してしまう(重要!!)
そのためにはどうすればいいのか?
いままでやってきたこと
Scalaの本を書いたり、翻訳したりした
SI企業で業務システム開発の事例を作った
Scalaでオープンソースプロダクトを開発
Scala関連イベントのサポート
Webサービス企業でそれなりの規模でScalaを採用した
突き当たった壁
関数型プログラミング
関数型プログラミング in Scala
基本的には副作用を使った手続き型プログラミング言語
関数型言語由来の様々な機能を備えている
for内包表記、暗黙的なモナド
Scalaz、Cats、Slickなどの関数型ライブラリ
どこまで関数型に寄せるか?という選択を迫られる
プログラミングスタイルだけでなく、ライブラリやフレームワークの
選定から考慮しなくてはならない
振り返ってみる
業務での開発で起きたこと
OSS開発で起きたこと
業務での開発で起きたこと
時間
Scala力
業務での開発で起きたこと
最初はみんな初心者
時間
Scala力
業務での開発で起きたこと
Scala力アップ!!
最初はみんな初心者
時間
Scala力
業務での開発で起きたこと
Scala力アップ!!
最初はみんな初心者
後から入ってくる人つらい!!
時間
Scala力
ハードルの高さ
こうあって欲しい(気持ちは理解できる)
時間
Java力
業務での開発で起きたこと
最初はみんなScala初心者だった
初期のメンバーが少しずつレベルアップしてくる
関数型プログラミングが取り入れられてくる
時代によってコードの傾向が違う
後から入ってくる人ほどつらくなってしまう
振り返り
Scalaはプログラマの成長にあわせてスタイルを変えられる
長期間、固定のチームで開発するのであればチームの成長にあ
わせて変化していくことができる
メンバーの増加・入れ替わりが激しい場合は変化が大きいとどん
どんハードルが上がっていってしまう
OSS開発で起きたこと
Elasticsearch-Hadoop、Apache PredictionIOなど
手続き型バリバリのプログラミングスタイル
varやmutableコレクション、whileループ、returnや例外なども多
用されている
OSS開発で起きたこと
コミッターのスキルが低いのか?そんなことはない
バックグラウンド、モチベーションの違い
SparkのためにScalaを使っている
振り返り
言語としてではなくプロダクトとしてのニーズが存在する
○○を使えば□□ができる、というフックは増やしていくべき
関数型プログラミングへのモチベーションが低い状況でどうScalaを使うか?
方針
そもそもScalaは副作用のある手続き型プログラミング言語であ
り、モナドを意識せずに使えるように設計されている
Scalaの便利な機能を使いつつ、関数型プログラミングに寄せす
ぎないようにする
使うべきか?使わないべきか?
● var、while● mutableコレクション
● return● 例外
● null● Option.get● for内包表記
● 型クラス
考えてみよう!!
var、while
var、while
ループ処理などでフラグやアキュムレータなどに使いがち
takeWhileやfoldLeftなどで代用可能だが取っつきづらい
var line = reader.readLine()
while(line != null) {
...
line = reader.readLine()
}
var、while
ループ処理などでフラグやアキュムレータなどに使いがち
takeWhileやfoldLeftなどで代用可能だが取っつきづらい
var line = reader.readLine()
while(line != null) {
...
line = reader.readLine()
}
var、while
メソッド内での利用であれば許容する
valはJavaでfinalをつけるかどうかくらいの感覚で使い分けるの
がよさそう
mutableコレクション
mutableコレクション
ループしながら詰め替えるような処理で使いがち
val list = ...
val map = mutable.Map("some" -> 0, "none" -> 0)
list.foreach { x =>
if (x.nonEmpty) {
map.put("some", map("some") + 1)
} else {
map.put("none", map("none") + 1)
}
}
mutableコレクション
ループしながら詰め替えるような処理で使いがち
val list = ...
val map = mutable.Map("some" -> 0, "none" -> 0)
list.foreach { x =>
if (x.nonEmpty) {
map.put("some", map("some") + 1)
} else {
map.put("none", map("none") + 1)
}
}
mutableコレクション
メソッド内での利用であれば許容する
戻り値として返す際にimmutableなコレクションに変換する
使わなくても済むものはimmutableなコレクションを使うように啓
蒙していく
val map = list.groupBy(_.nonEmpty)
.map { case (nonEmpty, values) =>
if(nonEmpty) "some" -> values.size
else "none" -> values.size
}
return
return
Ealry returnやループ処理中からのreturnなどが使われがち
def hello(names: Seq[String]): String = {
if(name.isEmpty) return ""
...
}
戻り値の型を明記しないといけなくなる
コンパイル後に例外(ControlThrowable)で実現されるケースが
ある
return
Ealry returnやループ処理中からのreturnなどが使われがち
def hello(names: Seq[String]): String = {
if(name.isEmpty) return ""
...
}
戻り値の型を明記しないといけなくなる
コンパイル後に例外(ControlThrowable)で実現されるケースが
ある
return
実際に問題になるケースは少ないので許容してもよいのでは
そもそも例外をThrowableでキャッチしない
try {
...
} catch {
case NonFatal(t) => ...
}
例外
例外
エラーを戻り値で返すか?Eitherなどで返すか?
def findUser(): Either[Exception, User] = {
try {
val user: User = ...
Right(user)
} catch {
case e: Exception => Left(e)
}
}
例外
エラーを戻り値で返すか?Eitherなどで返すか?
def findUser(): Either[Exception, User] = {
try {
val user: User = ...
Right(user)
} catch {
case e: Exception => Left(e)
}
}
例外
Eitherで返そうとすると
例外の発生は防げないので変換が必要になる
for内包表記やEitherTなどが登場してしまう
通常は積極的に例外に倒してしまってもよいのでは?
ただし非同期処理の場合は別
null
null
null
使うべき理由がまるでない
がくぞー先生に消されてしまう
Optionを使う
Javaライブラリを使う部分は仕方ない
Option.get
Option.get
値が必ず入っているはずだと直接getしがち
Noneチェックしてからgetしがち
val opt: Option[String] = ...
if(opt.isDefined){
val str = opt.get
...
}
Option.get
値が必ず入っているはずだと直接getしがち
Noneチェックしてからgetしがち
val opt: Option[String] = ...
if(opt.isDefined){
val str = opt.get
...
}
Option.get
opt.getOrElse("") などとする人が出現
getして明示的にエラーになる方がマシではある
mapやforeachなどを使うよう啓蒙していく
for内包表記
for内包表記
モナモナするときに使う
for {
project <- findProject(projectId)
user <- findUser(userId)
} yield {
registerComment(project, user, comment)
}
for内包表記
モナモナするときに使う
for {
project <- findProject(projectId)
user <- findUser(userId)
} yield {
registerComment(project, user, comment)
}
for内包表記
無理して使わない
コレクション操作やFutureのチェーンをシンプルに記述するため
に使う
その場合もmapやflatMapのシンタックスシュガーという認識があ
ればよい
「モナド」って言わない
型クラス
型クラス
implicitの使い方の一種
implicit val userInfoReads = Json.reads[UserInfoInfo]
r.body.validate[UserInfo].fold(
error => ...,
form => ...
)
sealed trait JsValue extends JsReadable {
def validate[A](implicit rds: Reads[A]): JsResult[A]
}
型クラス
implicitの使い方の一種
implicit val userInfoReads = Json.reads[UserInfoInfo]
r.body.validate[UserInfo].fold(
error => ...,
form => ...
)
sealed trait JsValue extends JsReadable {
def validate[A](implicit rds: Reads[A]): JsResult[A]
}
型クラス
フレームワークやライブラリで使っているケースが多い
使うだけであれば意識する必要はない(CanBuildFromを意識し
なくてもScalaのコレクションは使える)
play-jsonのReads/Writesのように自分で実装しないといけない
ケースもあるが、そこまで躓くことはない印象(面倒なのはさてお
き...)
「型クラス」って言わない
結果
● var、while● mutableコレクション
● return● 例外
● null● Option.get● for内包表記
● 型クラス
最終的にはケースバイケース
これはあくまでも判断の一例
プロダクトの方向性、今後のチーム運営も考えて決める
チームのスキルが一様であればそれにあわせればよい
Scalaを使う人がもっと増えて欲しい
最初はみんな初心者だった
Scalaで楽しくプログラミングをして欲しい
関数型プログラミングに興味のある人だけでなく、
それ以外の人たちにもScalaを使って欲しい
Scalaを使っているのにというジレンマを感じることもある
いろんな使い方ができるのもScalaの良いところ
Scalaユーザが増えることで自分たちの選択肢も広がる
ご静聴ありがとうございました