独学大学情報学部

ただのノート。主にプログラミング。

Lens&Prism勉強会でMonocleについて発表してきた

とっても今更なのだけど、記録として。

connpass.com

↑にLT枠で参加してきた。

資料は

www.slideshare.net

色々あって、前半部分参加できなかったのだけれど、TL眺める限りとても濃い話だったっぽいので、ちょっと残念。

http://togetter.com/li/828498

当日は予想以上の人で、緊張のため話そうと思ってたこと半分くらい飛んじゃって、テンパって足プルプルしてたけど、まぁなんとかなって良かった。

今回の発表内容は「理論はよく分からないけど、使い方分かってきたから実践で使ってみた」的な話だったので、もし次回があるのなら今度は理論的な話とかもできるようになりたいなーとか思ってます。

当日の準備、懇親会、片付けに一切参加できずですんませんでした、、、
また機会があれば、今度こそお手伝いできればと!

みなさんお疲れ様でした!ありがとうございました!!

「Scala勉強会第143回 SPECIAL DAY ハッカソン in 歌舞伎座」に参加してきた

今日は ↓ に参加してきました。
http://rpscala.doorkeeper.jp/events/20374

一番後ろの席に座ってたので人数数えてみると、ざっと30人弱くらいでした。
各々自己紹介の後、ハッカソンスタート。

今日やったこと

スライド作るの面倒だった時間なかったので、発表内容もここでまとめます。

↓今日はこれの開発してました。
https://github.com/aoiroaoino/connpasscala

ざっくり言うとConnpassってイベント支援サービスのAPIクライアント for Scalaです。既に返ってきたJsonをパースする部分は作っていたので、今回はHTTPリクエスト投げる部分+αを実装しました。 HTTPリクエストにはscalaj-httpJsonのパースにはargonautを使用しています。 特にこだわりがあったわけではなく、単純に使った事なかったので試したかったってのが理由。

使い方は以下のような感じです。(2015/02/12 現在)

scala> import connpasscala._, Connpasscala._
import connpasscala._
import Connpasscala._

scala> :paste
// Entering paste mode (ctrl-D to finish)

Client()
  .keyword("scala")
  .ownerNickname("AoiroAoino")
  .run()

// Exiting paste mode, now interpreting.

res0: Option[connpasscala.Response.Connpass] =
Some(Connpass(1,1,1,List(Event(10643,Scalive # 1.414 @ 西麻布ベース,Scalaなどについて語らいながら1.414人前の肉を喰らう@西麻布ベース,<h3>▼趣旨</h3>
<p>前回のScalive!<br>
...

scala> val event = res0.get.events.head
event: connpasscala.Response.Connpass.Event =
Event(10643,Scalive # 1.414 @ 西麻布ベース,Scalaなどについて語らいながら1.414人前の肉を喰らう@西麻布ベース,<h3>▼趣旨</h3>
...

scala> event.title
res2: String = Scalive # 1.414 @ 西麻布ベース

scala> event.hashTag
res3: String = scalive

基本的にはConnpass API仕様の検索クエリのキャメルケース名メソッドで検索条件を追加していって、run()で実行後、レスポンスフィールド名のキャメルケース名メソッドでデータにアクセスする感じです。

とりあえず動いている状態ですけど、エラー処理皆無だったりパッケージ構成変だったりテスト書いてないし依存関係切り分け出来てないし、etc...と、実用には程遠い状態orz ただまぁ、折角の(それなりに使えそうな?)初ライブラリなので、CIやらバージョン管理やらMaven Centra Repositoryへの公開やら、一通り試してみたいですね。

感想

今回は久しぶりのハッカソン参加かつ、進捗ダメじゃなくて発表まで行けたので良かった!!でもやっぱりスライドは作るべき。。なんとなく伝えきれなかった感ある。。。

(´-`).。oO(なんとか時間内に使える状態になって良かった...)

まとめ

主催のみなさん、参加者のみなさん、お疲れさまでした!ありがとうございました!    

Scalive#1.414 お疲れ様でした!

先日の土曜日にScalive#1.414が開催されました!

とてもオシャレな会場で生ビール片手に生ハムつまみながらLT大会を実施!
お酒に酔っていても推しメンとScalaを語る眼差しは真剣でした!(個人的視点

以下、生ハム原木があられもない姿になっていく様子です。ご査収ください。

f:id:Aoino:20150124152515j:plain

f:id:Aoino:20150124154702j:plain

f:id:Aoino:20150124202103j:plain


(´-`).。oO(5kgの生ハム、予想に反してまさかの完食)


せっかくなのでLTしました

ScalaとPlayの社内勉強会やってみた的な話しました。

で、本題は社内勉強会で作った資料をベースにScala/Play Framework初心者向けのチュートリアルを作って公開しましたって話です。まだちょっと荒い部分があるんですが、以下のGitHubアカウントで公開/管理してますのでよかったらどうぞ!

mvrck-inc/tutorial-scala-play · GitHub


最後に

会場を貸して下さったラフノートさん、そして参加者の皆さん、本当に楽しい時間をありがとうございました!次回のScaliveもお楽しみに!!



追記:
当日「なんでScalaと生ハム原木なんや!関係ないやろ!」ってツッコミを頂きました。

f:id:Aoino:20150124155003j:plain

実は生ハム原木を固定する台座にScalaって呼ばれる種類があるんですよ!(後付け
ちなみに、今回使用していたのは右下のBANQUETAでした!







 _人人人人人人人人人人人人_
 > やっぱり関係なかった <
  ̄ YYYYYYYYYYYYYYYY ̄

2014年を振り返って

2014年を振り返って

個人的な振り返りです。
特に面白い内容はありません!!

記事

今年書いたのはScala Advent Calendar 2014 - Qiitaの12/22の記事一本でした。

http://aoino.hatenablog.com/entry/2014/12/23/050932 書きたいネタは山ほどあったので、あとは記事を書く気力とまとめ能力ですね…きっと...

イベント

参加したイベントは(覚えてる範囲で)以下です。

参加したはいいものの、内容まとめやレポートを全然書けてないのでその辺も頑張りたいですね。アウトプット大事。

オープンソース

fix typo以外で初めてPull Request出しました!マージされました!

https://github.com/argonaut-io/argonaut/pull/154

前述のアドベントカレンダーネタでMonocle触ってる時にargonautの内部で古いバージョン使われてるの発見して出してみました。次の日起きたらMonocleの新バージョン出ててびっくりしたのはいい思い出。あと、英語力が…もっと…欲しい...

お仕事

新卒(相当)で入社した前職でしたが、11月一杯で退職しました。
昨年4月から数えて1年8ヶ月という非常に短い期間でしたが、どうしてもScalaでお仕事したかったのが理由です。 前職でとてもよくして頂いていただけに、お世話になった方々には感謝しても感謝しきれません。 本当にありがとうございました。

12月からは新しい場所でScala書いて頑張ってます!!

それなりには読んだ気がします。たぶん。
前半は主に言語とか情報工学系な内容の本で、後半はWeb系システムに関する本が多かったと思います。 帰省中なので、あとでタイトルはまとめようと思います。。。

来年の抱負

来年は主に英語とオープンソース活動に力を入れてみたいと思います。 きっと他に興味もったりするかもしれませんけれど、総じて楽しめればいいかなーって感じで。

あとは、やっとモナモナした話が分かってきた感じもするので来年度も引き続き関数型言語どっぷりな方針で頑張りたいです!

まとめ

今年も大変お世話になりました。来年もどうぞ宜しくお願い致します!
✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌

Monocleとかいうのがありまして

この記事はScala Advent Calendar 2014の22日目です。
日付変わっちゃいました、すみません。

今回はちょっとMonocle触ってみました。

Monocleとは

Julien Truffaut氏がメインで開発してるScalaでLensなライブラリです。

最新安定版はv1.0.1(2014.12.22 現在)で、つい先日リリースされたばかりのピチピチです。

もともとはHaskellのLensパッケージがあって、それをScalaで実装してみたみたいな感じです。 MonocleではLens, Traversal, Optional, Prism, Isoを提供していて、今回はその中からLensとPrismを紹介します。

あと、Lensに関しては圏論的なバックグラウンドがあるそうですが全然詳しくないのでこの記事では触れないことにします。

Lensはどういう時に欲しくなるのか

LensはJavaなどで言うgetterやsetterを抽象化した概念で、不変性を保ちつつネストしたデータ構造に対するアクセスをLensの合成で表現できるようにしたものになります。どういう事か見ていきましょう。

公式READMEのサンプルをちょいとお借りして、下記のようなネストしたデータ構造があったとします。

case class Street(name: String)
case class Address(street: Street)
case class Company(address: Address)
case class Employee(company: Company)

さて、Streetのnameを不変性を保ったまま書き換えたいときにどういったアプローチがあるでしょうか? いろいろな方法があると思いますが、case classであればcopyメソッドを使うのがいいでしょう。

val employee = Employee(Company(Address(Street("chuodori"))))

// Streetのnameをcapitalizeするっ
employee.copy(
  company = employee.company.copy(
    address = employee.company.address.copy(
      street = employee.company.address.street.copy(
        name = employee.company.address.street.name.capitalize
      )
    )
  )
)

おっと、不変性を保ったばかりにとても冗長なコードになってしまいました。 ちなみにvarだった場合はご想像の通りさっきのコードに比べてすっきりかけます。

// もし、変数がvarで再代入可能だったら...
case class Street(var name: String)
case class Address(var street: Street)
case class Company(var address: Address)
case class Employee(var company: Company)

val employee = Employee(Company(Address(Street("chuodori"))))

val capitalizedName = employee.company.address.street.name.capitalize
employee.company.address.street.name = capitalizedName

scala> employee
res0: Employee = Employee(Company(Address(Street(Chuodori))))

ちょいと長いので二行に分けちゃいましたが、copyメソッドを使ってた時よりも遥かにすっきり直感的です。

あぁ...これでは「varで良くね?」って言われても返す言葉がありません.....。不変性を保ちつつ上記varの時と同様にすっきりかけないものでしょうか.....。

そう!ここでLensの出番です!!

MonocleでLens

まずは、対象のデータに対してLensを定義してやる必要があります。
MonocleでのLensの定義方法は以下の3種類です。

  1. 手動で明示的に定義する
  2. Lenser マクロを使う
  3. @Lenses アノテーションを使う

ではそれぞれ試してみましょう。 ※Lensの定義はREPL上ではできないのでsbtにMonocleの依存追加してcompileしてやります。*1

1. 手動で明示的に定義する

import monocle.Lens

object SampleLens {

  // 対象のデータに対するgetterとsetterを引数に渡します。
  val _name = Lens[Street, String](_.name)(str => s => s.copy(name = str))
  val _street = Lens[Address, Street](_.street)(s => a => a.copy(street = s))
  val _address = Lens[Company, Address](_.address)(a => c => c.copy(address = a))
  val _company = Lens[Employee, Company](_.company)(c => e => e.copy(company = c))

}

さぁ、定義ができたら'sbt console'でREPLを立ち上げて実行してみましょう。

scala> import SampleLens._
import SampleLens._

scala> val employee = Employee(Company(Address(Street("chuodori"))))
employee: sample.SampleLens.Employee = Employee(Company(Address(Street(chuodori))))

// 値取得
scala> (_company composeLens _address composeLens _street composeLens _name) get employee
res1: String = chuodori

// 値書き換え
scala> (_company composeLens _address composeLens _street composeLens _name).set("hogedori")(employee)
res2: sample.SampleLens.Employee = Employee(Company(Address(Street(hogedori))))

// 関数の適用(capitalize)
scala> (_company composeLens _address composeLens _street composeLens _name).modify(_.capitalize)(employee)
res3: sample.SampleLens.Employee = Employee(Company(Address(Street(Chuodori))))

これが冒頭で言っていた「データに対するアクセスをLensの合成で表現できる」って話です。 ちなみに「composeLens」のエイリアスとして「^|->」が定義されているので7文字分すっきりできます。

scala> (_company ^|-> _address ^|-> _street ^|-> _name).modify(_.capitalize)(employee)
res5: sample.SampleLens.Employee = Employee(Company(Address(Street(Chuodori))))

不変性を保ちつつ、すっきり書けるようになりました!

2. Lenserマクロを使う

v0.5.1より追加された機能です。 手動で定義するよりも簡単にLensを定義してやることができます。

import monocle.macro.Lenser

object SampleLenserMacro {

  val lenser = Lenser[Employee]

  val _name = lenser(_.name)
  val _street = lenser(_.street)
  val _address = lenser(_.address)
  val _company = lenser(_.company)

}

使い方は1. 手動で明示的に定義した場合と同じなので省略します。

3. @Lensesアノテーションを使う

もっとも簡単にLensを「生成」してやる方法です。 case classの宣言に対して@Lensesアノテーションをつけると各変数に対してLensが定義されます。やばいです。

import monocle.macro.Lenses

object SampleLensesMacro {

  @Lenses
  case class Street(name: String)

  @Lenses
  case class Address(street: Street)

  @Lenses
  case class Company(address: Address)

  @Lenses
  case class Employee(company: Company)

}

ただし、この方法を使うには色々注意が必要です。 内部的にはマクロパラダイスマクロアノテーションを使っていて、プラグインの追加が必要だったり、そもそもcase classの定義に手を加える必要があるので、既に定義済みのcase class(ライブラリの方で定義されてるものなど)に使えなかったりします。 一応、実験的な機能と捉える方がいいでしょう。

また、基本的な使い方は1, 2と同じですが、Lensの生成先がコンパニオンオブジェクト内なのでちょっと面倒になります。

scala> import SampleLensesMacro._
import SampleLensesMacro._

scala> import Street._, Address._, Company._, Employee._
import Street._
import Address._
import Company._
import Employee._

scala> val employee = Employee(Company(Address(Street("chuodori"))))
employee: sample.SampleLenses.Employee = Employee(Company(Address(Street(chuodori))))

scala> (company ^|-> address ^|-> street ^|-> name).modify(_.capitalize)(employee)
res5: sample.SampleLenses.Employee = Employee(Company(Address(Street(Chuodori))))

Prismもあるんだよ?

さて、冒頭で述べましたが、Monocleが提供してるのはLensだけではありません。 Prismもその一つです。Prismはざっくりイメージで言うと「失敗を表現できるようになったLens」です。*2

実際のコード見てもらった方が早いですね。 Lensの時と同様にPrismの定義はREPL上ではできないのでsbtにMonocleの依存追加してcompileしてやります。*3

import monocle.Prism
import scalaz.Maybe

object SamplePrism {

  val strToInt: Prism[String, Int] = 
    Prism { str: String => Maybe.fromTryCatchNonFatal(str.toInt) }(_.toString)
}

'sbt console'でREPLを立ち上げて試してみましょう。

scala> import SamplePrism._
import SamplePrism._

scala> strToInt getMaybe ("1")
res1: scalaz.Maybe[Int] = Just(1)

scala> strToInt getMaybe ("")
res2: scalaz.Maybe[Int] = Empty()

res2でtoIntに失敗した場合はEmpty()が帰ってきているのがわかります。
ちなみに、以下のような使い方もできます。

scala> strToInt.reverseGet(1)
res12: String = 1

scala> strToInt.modify(_ + 100)("1")
res13: String = 101

scala> strToInt.modify(_ + 100)("")
res14: String = ""

reverseGetでInt => Stringをさせることができます。*4
res13は内部的には"1"をIntに変換した後100を足して再度文字列にしています。何も知らずに見ると結構やばいですね。 なお、今回自分で上記のstrToIntを定義しましたが、Scalaの基本型に対するPrismは既にMonocle側で定義されてたりします。

まとめとか

この記事を書き上げる前にMonocle v1.0.1がリリースされました。 自分が触りだした時はv0.5.1でしたが、絶賛開発中の1.x系と比べると引数の順序とかpackage構成とか結構違っていて、 その辺のハマりどころとか書いて「もうすぐリリースされるはずなので、1.x系の安定版を待ちましょう!!」みたいな感じで締めようと思ってたんですけど、そんな必要なくなっちゃいました。安心してv1.0.1を使いましょう!!

とかいいつつ、まだまだ全然全く本当の美味さを把握できてる気がしないので引き続き試してみたいです、はい。

あ、あとargonautの中でも使われていて面白そうなんですけど、全然追えてないのでまたの機会に。*5

 
 


*1:定義時に「object Lens does not take type parameters.」とかってエラー吐く.....

*2:概念的にはLensとPrismはそれぞれ別な方向へIsoを特化させたものであり、「Lensを拡張したのがPrismである」というわけではありません。とはいえ、Monocle内でIsoをベースにLens, Prismが実装されているのかって言うとそうでもないです。Isoに関しては別の機会に...

*3:strToIntが「monocle.Prism.type does not take parameters」とかってエラー吐く.....

*4:Prism定義時、第二引数に渡した関数が呼ばれてます。

*5:今度こそ.....更新頻度増やすんだ.....