めんどくさくない Scala #kwkni_scala

15,378 views

Published on

怖くない Scala 勉強会での発表資料です。

http://connpass.com/event/3420/

Published in: Technology
0 Comments
47 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
15,378
On SlideShare
0
From Embeds
0
Number of Embeds
6,462
Actions
Shares
0
Downloads
39
Comments
0
Likes
47
Embeds 0
No embeds

No notes for slide

めんどくさくない Scala #kwkni_scala

  1. 1. めんどくさくない Scala #kwkni_scala @seratch Kazuhiro Sera 1
  2. 2. 自己紹介的な ・Kazuhiro Sera @seratch ・最近は 2 歳児の子育てが忙しい ・JUnit Helper という Eclipse プラグイン ・Skinny Framework、ScalikeJDBC ・Ruby では Gistub とか Ruby Kaigi 2013 LT とか ・Scala Conference in Japan 2013 スタッフ ・Akasaka.scala #akskscala を運営(していた) 2
  3. 3. まず、あなたと Scala の出会いを 思い出してみてください。 3
  4. 4. 引用しますね... Team Geek ―Googleのギークたちは いかにしてチームを作るのか [単行本(ソフトカバー)] Brian W. Fitzpatrick (著), Ben CollinsSussman (著), 角 征典 (翻訳) 出版社: オライリージャパン (2013/7/20) ISBN-10: 4873116309 ISBN-13: 978–4873116303 発売日: 2013/7/20 4
  5. 5. “初心者にとってソフトウェアとは どのようなものだろうか?” “ソフトウェアはユーザーのことを 歓迎しているだろうか?” Team Geek 「6.1.1 第一印象に注目する」より引用 5
  6. 6. “使ってもらいやすくなっているだろうか?” “熟練者にとって使いやすそうで 実用的なものになっているだろうか?” Team Geek 「6.1.1 第一印象に注目する」より引用 6
  7. 7. “すぐに生産性の向上を アピールできるだろうか?” “それとも学習曲線が急で 何度も涙が出るようなものだろうか?” Team Geek 「6.1.1 第一印象に注目する」より引用 7
  8. 8. “もっと具体的に言うと、 最初の 30 秒間のユーザーエクスペリエンス はどのようなものだろうか?” “ここでは感情的な答えが聞きたい。 1 分後にどのように感じるだろうか?” Team Geek 「6.1.1 第一印象に注目する」より引用 8
  9. 9. ここで質問。 9
  10. 10. 初めて Scala でコードを書いてみたとき どうだったか覚えていますか? とりあえず Hello World を print した後 次に何をやったっけ?何を調べたっけ? 10
  11. 11. フレームワーク、ライブラリ、ツール・・ どれもすんなりと扱えて、すごく便利で 「これは るwww」と思いましたか? それとも Try & Error で 頑張りましたか? 11
  12. 12. 自分がまだ不勉強なせいなのか それとも ”それ” が不親切だからなのか 初心者には区別がつかない。 入口は、極力ハマりポイントをなくして すぐに試せる親切な手順が書いてあって “ゆとり仕様” くらいでちょうどいい。 12
  13. 13. そこを補完する Scala 逆引きレシピは すばらしいですね!今すぐ買いましょう!! Scala逆引きレシピ (PROGRAMMER’S RECiPE) [単行本(ソフトカバー)] 竹添 直樹 (著), 島本 多可子 (著) 出版社: 翔泳社 (2012/7/3) 言語 日本語 ISBN-10: 4798125415 ISBN-13: 978–4798125411 発売日: 2012/7/3 13
  14. 14. で、ここからが本題。 14
  15. 15. “「Scala は る!」を根付かせたい” Scala は書いていて楽しいし、一番好きな言語だけど いくつか面倒なことや残念なことがあります。 そのめんどくさいことを減らすための提案と それを意識して私がつくっているものを 少し紹介したいと思います。 15
  16. 16. 一般論としてのめんどくささ 16
  17. 17. めんどくさいのはダメ ・めんどくさいものを頑張って使うのは有害である ・本題に集中できず、他のミスを誘発しやすい ・奥が深い症候群に陥りやすい ・今まで投資した時間を正当化するために固執しがち ・たいがいそのノウハウのポータビリティは低い ・そもそも楽しくない(人生は短い)  結果、確実に生産性が低下する(自明) 17
  18. 18. Scala でめんどくさいこと? 18
  19. 19. すぐに思いつくこと... ・コンパイルが遅すぎる ・Scala、sbt バージョン非互換 ・言語仕様の学習が大変そう ・スタイルが統一されなさそう ・そもそも JVM な時点でちょっと(ry 19
  20. 20. Compile! Compile! Compile! 20
  21. 21. コンパイルが遅すぎる ・良いマシンを...(震え声) ・sbt の ~;compile とか ~;test でごまかす ・コンパイルが遅くなる言語機能を避ける... ・何でもかんでもコンパイルしない(個人の意見です) 「何でもかんでもコンパイルしない」について Skinny Framework のデモをして説明します 21
  22. 22. デモ:Yeoman って知ってますか? Node.js で動く Rails generator 的なもの。 Windows でも Node と Ruby 入れれば使えるはず。 brew install nodejs npm install -g yo npm install -g generator-skinny mkdir hello-skinny cd hello-skinny yo skinny ./skinny run 22
  23. 23. もう重たくない Scala 開発 ・Controller、Model は Jetty 起動なしでテスト可能 ・DB 連携は実行せずに正しさを確認できないので、お決 まりな CRUD は(ある程度)動的でもよい ・View の調整が圧倒的に多いので、いちいちコンパイル せず実行時エラーで確認(& Controller のテストを CI) ・CoffeeScript なども開発 mode は実行時変換(予定) ・production だけ Scalate precompile も可能 ・サンプルはこちらにあります 23
  24. 24. 24
  25. 25. Scala、sbt の非互換 ・今使っているものは足かせにならないか? ・ライブラリ、sbt プラグインは厳選した方がよい ・ワーストケースで移行が容易な実装・テストにしておく ・ ほとんどいじらない共通部分はあえて Java で書いてメ ンテコストを抑える(Scala 側でラップは必要) ・フリーライダーで乗り切るのは多分無理(GitHub 時代 の言語、自ら貢献する姿勢で) 25
  26. 26. 26
  27. 27. 言語仕様の学習が大変そう ・最低限必要なレベルまではそれほど大変ではないはず ・コップ本の前にもっとライトな本を流してもよい ・”Scala for the impatient” あたりがオススメ ・scalatutorials.com で手を動かすとか ・困ったら scalajp の ML で質問(怖くない) 27
  28. 28. TMTOWTDI? “There’s more than one way to do it” 28
  29. 29. スタイルが統一されなさそう ・書き方は基本的に Scala Style Guide に従う ・scalariform は問答無用で適用する ・汎用コーディング規約は存在しないので、チームの中で の共通認識をすりあわせていく ・理解度の一番低い人に合わせすぎない ・何かに感染している人を周りは生暖かく見守る ・歩み寄る 、ストレスを溜めない 29
  30. 30. 30
  31. 31. そもそも JVM な時点で(ry ・LL だったらもっと簡単なのに... と思われたら負け ・sbt 初回起動... 巨大な依存ライブラリは極力減らす ・cs や g8 は好きだけど sbt だけで済まないのは微妙... ・ライブラリを作るなら REPL で簡単に試せることは重視 する(import はなるべく少なく) ・今日から Scala を始めたとしてどう思うか? 31
  32. 32. あなたの “Scala ではこれが普通” は ただ慣れてしまっただけかもしれない... 一度、見直してみると... 32
  33. 33. ここからは宣伝。 33
  34. 34. RDB とのやり取り 34
  35. 35.  No More SQL? Really? ・「SQL ならもう分かってるけど、このライブラリでどう 書けばいいか分からん... ドキュメントにないな... サンプル 探すか... ない... ソース読むか...」(時間の無駄) ・Anorm のコンセプト “You don’t need another DSL to access relational databases.” 自体には共感する ・RDB 使ってて SQL と手を切れるなんて幻想ですよね 35
  36. 36. ScalikeJDBC ・SQL 書きたいときは sql”select name from companies where id = ${id}” のように書く ・SQLInterpolation では必ずバインド変数になるので SQL インジェクション脆弱性がないと断言できる ・1.6 から select.from(Company as c).where.eq(c.id, 123) のように SQL から乖離しない DSL をサポート ・QueryDSL は IDE での補完にも強い(Dynamic + macros に時代が追いついてないけど) 36
  37. 37. SQLInterpolation val ids = Seq(1, 2, 3) case class Member(id: Long, name: Option[String]) DB readOnly { implicit s => sql”select id, name from members where id in (${ids})” .map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) } .list.apply() } object Member extends SQLSyntaxSupport[Member] val m = Member.syntax(“m”) DB readOnly { implicit s => sql”select ${m.result.*} from ${Member as m} where ${m.id} in (${ids})” .map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) } .list.apply() } 37
  38. 38. QueryDSL val ids = Seq(1, 2, 3) case class Member(id: Long, name: Option[String]) object Member extends SQLSyntaxSupport[Member] val m = Member.syntax(“m”) DB readOnly { implicit s => withSQL { select.from(Members as m).where.in(m.id, ids) } .map { rs => Member( id = rs.long(m.resultName.id), name = rs.stringOpt(m.resultName.name)) } .list.apply() } 38
  39. 39. 「あれ?わかりやすい!」 「めんどくさくなさそう!」 (心の声) 39
  40. 40. いろんな SQL を... // Squeryl join(Student, Club.leftOuter)((s, c) => where(c.map(_.id) isNull) select(s) on(s.clubId === c.map(_.id))) select * from students s left join clubs c on s.club_id = s.id where c.id is null // Slick for { (s, c) <- Student leftJoin Club.map(_.??) on (_.clubId === _._1) if c._1.isNull } yield (s, c) // ScalikeJDBC val (s, c) = (Student.syntax(“s”), Club.syntax(“c”)) select.from(Student as s).leftJoin(Club as c).on(s.clubId, c.id).where.isNull(c.id) 40
  41. 41.  one-to-x API ・one-to-x API が実は結構便利 ・sql”...”.one(..).toMany(...).list.apply() のようにして 複数のテーブルを join してもきれいに取ってこれる ・one-to-one は無制限だが one-to-manies は現状だと 最大 5 個のテーブルまで結合可能という制限がある(実装 依存なので必要なら要望ください) 41
  42. 42. one-to-x 例 val pg: Option[Programmer] = withSQL { select.from(Programmer as p) .leftJoin(Company as c).on(p.companyId, c.id) .leftJoin(ProgrammerSkill as ps).on(ps.programmerId, p.id) .leftJoin(Skill as s).on(sqls.eq(ps.skillId, s.id).and.isNull(s.deletedAt)) .where.eq(p.id, id).and.isNull(p.deletedAt) }.one(Programmer(p, c)) .toMany(Skill.opt(s)) .map { (pg, skills) => pg.copy(skills = skills) } .single.apply() // one(...).toOne(...) // one(...).toManies(...) 42
  43. 43. Debugging & Testing ・ソースコードを自動生成する sbt プラグインもある (sbt “scalikejdbc-gen [table-name]”) ・ActiveRecord のようなクエリログ出力、ログ出力だけ でなく Fluentd など外部に送ったりもできる ・テスト重視、AutoRollback と Fixture を ScalaTest、 spec2 に提供している 43
  44. 44. クエリログ ・遅いクエリの内容だけでなく、そのクエリがコードのど こで発行されたかまで分かる [debug] s.StatementExecutor$$anon$1 - SQL execution completed [Executed SQL] select * from users where email = 'guillaume@sample.com'; (2 ms) [Stack Trace] ... models.User$.findByEmail(User.scala:26) controllers.Projects$$anonfun$index$1$$anonfun$apply$1$$anonfun$apply$2.apply(Projects.scala:20) controllers.Projects$$anonfun$index$1$$anonfun$apply$1$$anonfun$apply$2.apply(Projects.scala:19) controllers.Secured$$anonfun$IsAuthenticated$3$$anonfun$apply$3.apply(Application.scala:88) controllers.Secured$$anonfun$IsAuthenticated$3$$anonfun$apply$3.apply(Application.scala:88) play.api.mvc.Action$$anon$1.apply(Action.scala:170) 44
  45. 45. ScalaTest 例 ・fixture パッケージの Spec であれば AutoRollback trait を mixin して fixture と auto rollback が可能 class MemberSpec extends fixture.FlatSpec with AutoRollback { // fixture loading before each test override def fixture(implicit session: DBSession) { sql”insert into members values (1, ‘Alice’)”.update.apply() } it should “work” in { implicit session => // do something and will be rolled back } } 45
  46. 46. specs2 例 ・unit は以下の通り、acceptance スタイルもサポート (ただし case class xxx() というちょっと変態的な...) object MemberSpec extends Specification { trait AutoRollbackWithFixture extends AutoRollback { // fixture loading before each test override def fixture(implicit session: DBSession) { sql”insert into members values (1, ‘Alice’)”.update.apply() } } “it should work” in new AutoRollbackWithFixture { // do something and will be rolled back } 46
  47. 47.  Play2 サポート ・Play2 との連携も標準でサポートしている ・migration は evolution ではなく flyway 併用を推奨 ・model 層は完全に Play2 非依存にできる(FakeApp 不 要、共通化してバッチや他アプリに流用も可能) ・Typesafe Activator で Play2 サンプルを試せる 47
  48. 48.  ドキュメント ・公式ドキュメントは英語の Wiki ですが、私のブログに かなり日本語情報があります ・ Kindle Direct Publishing で Cookbook(日本語)発 売中(GitHub でも読めるので 200 円は投げ銭です) ・Google Group の ML あります ・日本語 ML もあります 48
  49. 49. Go Reactive! ・non-blocking な ScalikeJDBC-Async もあるよ ・@mauricio の postgresql/mysql-async ・直接使うと結構めんどうなのを頑張って吸収している ・トランザクションも map/for 式 で表現可能 ・JDBC ほど枯れてないので、ガチで使うなら人柱覚悟で 49
  50. 50. スター乞食 気に入ったら今すぐに http://git.io/scalikejdbc へ行って star を押してください!!! 50
  51. 51. 普通の Web アプリ開発 51
  52. 52. Play2 で管理画面? ・正直、いろいろ無理あるよね... ・Servlet 捨てなくていいとき多いよね... ・Play2 は C10K 問題を克服するための次世代の Web フ レームワークを目指してわざわざリライトされたことを思 い出そう(Play の ML で宣言) ・個人的には Play2 は JSON API サーバなどが最も適して いると思っている 52
  53. 53. なら Scalatra で ・Sinatra よりは機能が豊富とはいえ、同じジレンマ ・色々やり方決めたり、他のものと組み合わせたり・・ ・管理画面みたいなお決まりのものなら、色々考えたり、 決めごとつくったり、検証したりせずに済ませたい ・でもフレームワークによる制限があると困る 53
  54. 54. Go Skinny! ・”Scala on Rails” を目指している Web フレームワーク (成り立ちはどちらかというと Padrino 的) ・Scalatra + ScalikeJDBC を土台とし、それらをスムーズ に連携させる glue code でできている ・ View Template の Precompile を否定、コンパイル待 ちを減らしてサクサク開発できることを重視 ・テスタビリティを重視(session の mock もサポート) 54
  55. 55.  Rails 的 Controller ・Controller は ScalatraFilter の拡張、Rails のアレがだ いたいある(まだないものもこれから随時対応) ・Validation は Command ではなく独自 ・Controller にルーティング設定を埋め込まず、メソッド 定義するだけにするスタイルを推奨 ・SkinnyResource という Rails の ActiveResource 相当 もあって ROA なアプリならこれが楽、Rails scaffold 互換 で XML、JSON レスポンスの URL まで自動生成 55
  56. 56. Controller & Routing class MembersController extends SkinnyController { def index = { set(“members”, Member.findAll()) // set in the request scope render(“/members/index”) // expects /views/members/index.html.ssp } } val members = new MembersController with Routes { get(“/members/?”)(index).as(‘index) // ‘index: action name } class ScalatraBootstrap extends SkinnyLifeCycle { override def initSkinnyApp(ctx: ServletContext) { ctx.mount(members, “/*”) } } 56
  57. 57. View // WEB-INF/views/members/index.html.ssp <%@val members: Seq[Member] %> <ul> #for (member <- members) <li>${member.name}</li> #end </ul> // WEB-INF/views/members/index.html.scaml -@val members: Seq[Member] %ul - for (member <- members) %li #{member.name} 57
  58. 58. before/afterAction class MembersController extends SkinnyController { protectFromForgery() beforeAction(only = Seq(‘index)) { set(“members”, Member.findAll()) } def index = { render(“/members/index”) } } val members = new MembersController with Routes { get(“/members/?”)(index).as(‘index) } 58
  59. 59. Form/Validation class MembersController extends SkinnyController { def createForm = validation( paramKey(“name”) is required, paramKey(“companyId”) is required & numeric) def create = { if (createForm.validate()) { val id = Member.createWithPermittedAttributes(params.permit( “name” -> ParamType.String, “companyId” -> ParamType.Long)) flash += “notice” -> “Created!” redirect(s”/members/${id}”) } else { render(“/members/new”) } } } 59
  60. 60. 「あれ?わかりやすい!」 「めんどくさくなさそう!」 (心の声) 60
  61. 61. SkinnyResource object MembersController extends SkinnyResource { protectFromForgery() override def model = Member override def resourcesName = “members” override def resourceName = “member” override def createForm = validation(paramKey(“name”) is required) override def createFormStrongParameters = Seq(“name” -> ParamType.String) override def updateForm = validation(paramKey(“name”) is required) override def updateFormStrongParameters = Seq(“name” -> ParamType.String) } class ScalatraBootstrap extends SkinnyLifeCycle { override def initSkinnyApp(ctx: ServletContext) { ctx.mount(MembersController, “/*”) } } 61
  62. 62. SkinnyResource URIs GET /members GET /members/ GET /members.xml GET /members.json GET /members/{id} GET /members/{id}.xml GET /members/{id}.json GET /members/new POST /members POST /members/ GET /members/{id}/edit POST /members/{id} PUT /members/{id} PATCH /members/{id} DELETE /members/{id} 62
  63. 63. SkinnyResource Views ・SkinnyResource の場合は、以下のような命名規約で ssp/scaml/jade などのファイルを置く src/main/webapp/WEB-INF layouts/default.ssp views/members/index.html.ssp views/members/new.html.ssp views/members/edit.html.ssp views/members/show.html.ssp 63
  64. 64. 「クソ便利すぎワロタwww」 「めんどくさくなさそう!」 (心の声) 64
  65. 65. 65
  66. 66.  Skinny ORM ・ActiveRecord 的な簡便さを追求 ・単体でも利用可能(Play2 でも使える) ・裏側は ScalikeJDBC、その機能もフルで使える ・var なしで entity を定義できる ・Association はなるべく join でとってくる思想 ・Eager loading、ネストした Association は未対応 ・FactoryGirl(DB テストデータ作成サポート) ・DBSeeds でお手軽 migration、ちゃんとしたのも予定 66
  67. 67. Model case class Member(id: Long, name: Option[String]) object Member extends SkinnyCRUDMapper[Member] { def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member( id = rs.long(m.id), name = rs.stringOpt(m.name) ) } Member.createWithAttribute(‘name -> “Alice”) Member.findAllBy(sqls”name is not null”) Member.where(‘name -> “Alice”).limit(10).offset(0) val member = Member.findById(123) member.map(_.copy(name = “Bob”).save()) Member.updateById(123).withAttributes(‘name -> “Bob”) Member.deleteById(234) 67
  68. 68. FactoryGirl ・factory_pal は instance をつくるだけ、こっちは DB に データ投入もしてくれる(本来の #create) // src/test/resources/factories.conf member { name=”Anonymous” } // XXXSpec.scala val anon: Member = FactoryGirl(Member).create() val chris: Member = FactoryGirl(Member).create(‘name -> “Chris”) val factory = FactoryGirl(Member).withValues( ‘createdAt -> new DateTime(2011, 6, 22, 13, 46)) val eric: Member = factory.create(‘name -> “Eric”) 68
  69. 69. Associations case class Member(id: Long, name: Option[String], companyId: Option[Long], company: Option[Company] = None, skills: Seq[Skill] = Nil) object Member extends SkinnyCRUDMapper[Member] { belongsTo[Company](Company, (m, c) => m.copy(company = c)).byDefault val skills = hasManyThough[Skill](MemberSkill, Skill, (m, ss) => m.copy(skills ss)) def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member( rs.long(m.id), rs.stringOpt(m.name), rs.longOpt(m.companyId) ) } Member.findById(123) // with company Member.joins(Member.skills).findById(123) // with company, skills 69
  70. 70.  Skinny の今後 ・C10K クリアしないとまずいなら Typesafe Platform で ・Scala に Rails 必要?Rails と同じくらい楽に実装でき て、かつ型があれば相当嬉しいはずと思っている ・認証 API デザインとか手伝ってくれる人募集中 ・私が仕事に投入したら API を fix して 1.0 にする予定 (おそらく遅くとも今年度中) ・Better Java としての Scala にはまだまだ可能性ある 70
  71. 71. スター乞食 気に入ったら今すぐに http://git.io/skinny へ行って star を押してください!!! 71
  72. 72. 宣伝のまとめ ・ScalikeJDBC は現在 1.6.10、業務利用実績多数で十分 に stable、production ready です ・ScalikeJDBC-Async は現在 0.2.8、まだ α version で本 番実績は少ない、人柱上等な人が使ってください ・Skinny Framework は 0.9.4、おおまかなアーキテクチ ャはほぼ固まったが、内部 API 改善と機能追加の真っ最 中、2013 4Q までには 1.0 を出したいと思ってます 72
  73. 73. ぼっちはイヤだ!!! 一緒に る Scala をつくりましょう! 73

×