Scalaz!

@halcat0x15a

自己紹介

今日はScalazについて話します。

JIT(Just In Tsukkomi)は大歓迎。

いま話題のScalaz!

皆さんの声

  • 難しい!
  • 怖い!
  • 変態!
  • Haskell!
  • ☆!

Scalazってなあに?

githubには

“An extension to the core scala library.“

と書いてある。

まあ、コードを見ればわかるのではないか?

使い方

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala>

簡単そうだね!

何ができるようになったか

scala> 1 |+| 1
res0: Int = 2

scala> 'A === 'A
res1: Boolean = true

scala> 1.0.some
res2: Option[Double] = Some(1.0)

scala> 

(;゚Д゚)!?

何が起きているのか

IntSymbol,Doubleに存在しないメソッドが呼べた => implicit conversion!

どんなものに変換されているのだろう?

調べる

scala> (1: { def some: Option[Int] }).getClass
res16: java.lang.Class[_ <: AnyRef] = class scalaz.Identity$$anon$1

どうやらIdentityとやらに変換されている模様

Identity

scala> (1: Identity[Int])
res19: scalaz.Identity[Int] = 1

scala> res19.
/==             ===             ??              ?|?             asInstanceOf    assert_===      assert_≟        canEqual        cons            
constantState   doWhile         dual            equal           equalA          equalBy         fail            failNel         gt              
gte             isInstanceOf    iterate         leaf            left            logger          lt              lte             mapply          
matchOrZero     max             min             node            ok              pair            print           println         pure            
pureUnit        repeat          replicate       right           set             show            shows           snoc            some            
squared         state           success         successNel      text            toString        unfold          unfoldTree      unfoldTreeM     
unit            value           whileDo         wrapNel         zipper          |+|             |>              η               σ               
≟               ≠               ⊹               

scala> res19.

なんだか変な名前のメソッドがいっぱいですね!

自分で定義したクラスを試す

scala> case class ScalaChan()
defined class ScalaChan

scala> ScalaChan() === ScalaChan()
<console>:16: error: could not find implicit value for parameter e: scalaz.Equal[ScalaChan]
              ScalaChan() === ScalaChan()
                          ^

scala> 

Equal[ScalaChan]implicit valueが見つからない。

===の定義

sealed trait Identity[A] extends Equals with IdentitySugar[A] {
  def value: A

  def ===(a: A)(implicit e: Equal[A]): Boolean = e equal (value, a)

Equalの定義

trait Equal[-A] {
  def equal(a1: A, a2: A): Boolean
}

シンプル!

Type Class

このEqualというのは一般的に型クラスと呼ばれるものです。

型クラスはある型に対して性質を定義することができます。

Equalは同値比較をするための型クラスです。

論よりコード

scala> implicit lazy val ScalaChanEqual: Equal[ScalaChan] = new Equal[ScalaChan] {
     |   def equal(a1: ScalaChan, a2: ScalaChan): Boolean = a1 == a2
     | }
ScalaChanEqual: java.lang.Object with scalaz.Equal[ScalaChan]

scala> ScalaChan() === ScalaChan()
res21: Boolean = true

scala> Identity(ScalaChan()).===(ScalaChan())(ScalaChanEqual)
res22: Boolean = true

scala> 

ScalaChanEqualを作ったことでScalaChan===が利用できるようになりました。

もう一つ例

scala> implicit lazy val ScalaChanShow: Show[ScalaChan] = new Show[ScalaChan] {
     |   def show(a: ScalaChan): List[Char] = "Scalaちゃん".toList
     | }
ScalaChanShow: scalaz.Show[ScalaChan] = <lazy>

scala> ScalaChan().shows
res0: String = Scalaちゃん

scala> Identity(ScalaChan()).shows(ScalaChanShow)
res1: String = Scalaちゃん

scala> 

このようにScalazではimplicit valueを定義していくことで、利用できる関数が増えていきます。

うれしいところ

  • もとのデータに変更を加えることなく拡張が可能
  • 必要最低限のものを定義するだけで高度な関数を利用可能

    • これには複数の性質を定義することが必要な場合も

複数の性質を使う

すべての要素にある値を加算する

普通に書く

scala> def mapAppend(s: Seq[Int])(i: Int): Seq[Int] = s.map(_ + i)
mapAppend: (s: Seq[Int])(i: Int)Seq[Int]

scala> mapAppend(List(1, 2, 3))(5)
res0: Seq[Int] = List(6, 7, 8)

scala> mapAppend(Vector(1, 2, 3))(5)
res1: Seq[Int] = Vector(6, 7, 8)

scala> 

Int以外にDoubleやStringも対応させたい。

結合二項演算が必要。

Semigroup

scala> def mapAppend[A: Semigroup](s: Seq[A])(a: A): Seq[A] = s.map(_ |+| a)
mapAppend: [A](s: Seq[A])(a: A)(implicit evidence$1: scalaz.Semigroup[A])Seq[A]

scala> mapAppend(List(1, 2, 3))(5)
res2: Seq[Int] = List(6, 7, 8)

scala> mapAppend(List("Hello", "Real"))("World")
res3: Seq[java.lang.String] = List(HelloWorld, RealWorld)

scala> 

Seq以外にも対応したい。

各要素に関数を適用する関数(map)が必要。

Functor

scala> def mapAppend[M[_]: Functor, A: Semigroup](m: M[A])(a: A): M[A] = m.map(_ |+| a)
mapAppend: [M[_], A](m: M[A])(a: A)(implicit evidence$1: scalaz.Functor[M], implicit evidence$2: scalaz.Semigroup[A])M[A]

scala> mapAppend(List(1, 2, 3))(5)
res4: List[Int] = List(6, 7, 8)

scala> mapAppend(Option("Hello"))("World")
res5: Option[java.lang.String] = Some(HelloWorld)

scala> 

解決済みのmapAppend

def mapAppend[M[_], A](m: M[A])(a: A)(implicit f: Functor[M], s: Semigroup[A]) =
  maImplicit(m).map(x => (mkIdentity(x) |+| a)(s))(f)

mapAppend[Option, String](
  Option("Hello")
)(
  "World"
)(
  Functor.OptionFunctor, Semigroup.StringSemigroup
)

Scalazの基本

  • 性質を定義する
  • 性質を利用する

Scalazに関する基礎知識

重要な3つの型

implicit conversionによりScalazの主な関数を提供します。

  • Identity

    • すべての型
  • MA

    • 型パラメータを1つとる型
  • MAB

    • 型パラメータを2つとる型

命名規則

クラス名+W

Wrapper Class

型クラス名+s

implicit conversion, factory method

ScalazオブジェクトにMix-inされる。

型クラス名+Low

implicit function

型クラス自身が継承する。

型名+Sugar

function sugar

例のunicode文字の関数が定義されたもの。

Scalazのドキュメントはこれらを抑えておけば読めるようにます。

最後に

一人Scalaz Advent Calendarやってます。

少しでもScalazの日本語の情報が増えたらなと思います。

ご清聴ありがとうございました

mapAppendの解説

最初のコード

def mapAppend[M[_]: Functor, A: Semigroup](m: M[A])(a: A) = m.map(_ |+| a)

implicit parameterの略記法を使わない

def mapAppend[M[_], A](m: M[A])(a: A)(implicit f: Functor[M], s: Semigroup[A]) = 
  m.map(_ |+| a)

アンダースコアを使わない

def mapAppend[M[_], A](m: M[A])(a: A)(implicit f: Functor[M], s: Semigroup[A]) = 
  m.map(x => x |+| a)

implicit conversionの解決

def mapAppend[M[_], A](m: M[A])(a: A)(implicit f: Functor[M], s: Semigroup[A]) = 
  maImplicit(m).map(x => mkIdentity(x) |+| a)

implicit valueを明示的に渡す。

def mapAppend[M[_], A](m: M[A])(a: A)(implicit f: Functor[M], s: Semigroup[A]) =
  maImplicit(m).map(x => (mkIdentity(x) |+| a)(s))(f)

mapAppendのimplicit parameterを解決する

mapAppend(List(1, 2, 3))(5)(Functor.TraversableFunctor, Semigroup.IntSemigroup)

mapAppend(Option("Hello"))("World")(Functor.OptionFunctor, Semigroup.StringSemigroup)