ぶろぐ

日記です

Monoidを使ってみた

Semigroup(半群)に単位元を加えたものがMonoid(単位的半群)。Semigroupは結合法則を持つ。

ざっくり言うと「足し算のやり方と、ゼロはなんだ?」というのを持っていればMonoid。

import scalaz._
import Scalaz._

object HelloMonoid extends App {

  case class Hoge(cost: Int, cv: Int)

  implicit object HogeMonoid extends Monoid[Hoge] {
    def zero: Hoge = Hoge(0, 0)
    def append(r1: Hoge, r2: => Hoge): Hoge = Hoge(r1.cost + r2.cost, r1.cv + r2.cv)
  }

  Console println Hoge(100, 2) |+| Hoge(50, 3)
  //Hoge(150,5)

  val hs = Hoge(100, 2) :: Hoge(200, 3) :: Nil

  Console println hs.reduce(_ |+| _)
  //Hoge(300,5)

  Console println hs.foldLeft(HogeMonoid.zero)(_ |+| _)
  //Hoge(300,5)


  case class Foo(s: String, xs: List[Int])

  implicit object FooMonoid extends Monoid[Foo] {
    def zero: Foo = Foo("", List.empty)
    def append(r1: Foo, r2: => Foo): Foo = Foo(s"${r1.s} and ${r2.s}", r1.xs ::: r2.xs)
  }

  val fs = Foo("apple", List(1, 2)) :: Foo("ornge", List(5, 9)) :: Foo("banana", List(0)) :: Nil

  Console println fs.reduce(_ |+| _)
  //Foo(apple and ornge and banana,List(1, 2, 5, 9, 0))
}

zipしてmap

// 100円のりんご、200円のもも、150円のレモンを
// それぞれ5個、10個、15個購入した時の値段を求めるようなやーつ
// とあるプログラミング問題で使ったのでメモ
object zip {
  def main(args: Array[String]) {
    val point = List(100, 200, 150)
    val count = List(5, 10, 15)

    val result = point zip count map { case(p, c) => p * c }
    
    val total = result.reduce(_ + _)
    
    Console println total
  }
}

map

haskell

fmap
Prelude> fmap (+3) (Just 2)
Just 5

中置記法版
Prelude Data.Functor> (+3) <$> (Just 2)
Just 5
scala

mapで
scala> Option(2) map (_+3)
res3: Option[Int] = Some(5)

scalazの記号で
scala> 2.some &#8728; (_ + 3)
res5: Option[Int] = Some(5)

エスケープされちゃうな…数学の○みたいなやつを意識して作られたんだとおも。

curry化と関数オブジェクト

defで定義したメソッドは関数オブジェクトじゃないらしい。toStringした時にとかとかになるのがファーストクラスオブジェクトな関数っぽい。
(Function2トレイトとかのtoStringでそう実装されておる)
https://github.com/scala/scala/blob/2.11.x/src/library/scala/Function2.scala#L56

ここで関数オブジェクトをカリー化してみよう
scala> val func3 = (x1: Int, x2: Int, x3:Int) => x1 + x2 + x3
func2: (Int, Int, Int) => Int = <function3>

scala> func3.curried
res18: Int => (Int => (Int => Int)) = <function1>

問題ない。

def定義関数をカリー化してみよう
scala> def func3(x1: Int, x2: Int, x3:Int) = x1 + x2 + x3
func2: (x1: Int, x2: Int, x3: Int)Int

scala> func3.curried
<console>:15: error: missing arguments for method func3;
follow this method with `_' if you want to treat it as a partially applied function
              func3.curried
              ^

ほう・・・

エラーが起きるので一度関数オブジェクトにする
scala> (func3 _)
res34: (Int, Int, Int) => Int = <function3>

scala> (func3 _).curried
res31: Int => (Int => (Int => Int)) = <function1>

defで定義した関数をカリー化したいときは、 _ で一度関数オブジェクトにしてカリーかすればいい気がしたけどあっているのかな…?
あと、カリー化は引数の束縛と言うよりは1引数関数にすることで関数型プログラミングするときに都合がいい、っていう雰囲気がする。

ふつうのコトをふつうに

[0,100,200] というListを受け取って、MySQLに0〜99, 100〜199のidを検索するクエリを組みたい。
forを回してid1, id2で受け取って処理するっていう感じかーというC言語脳で考えつつ、値をスライディングしていくんだねというところに注目してscalaのコレクション探したらslidingって関数があった。

scala> List(1,2,3,4,5).sliding(2).foreach( x=> println( s"${x.head} : ${x.last}" ) )
1 : 2
2 : 3
3 : 4
4 : 5

こんなかんじっすよね

scala> List(0,100,200).sliding(2).foreach( x=> println( s"(value >= ${x.head} and value < ${x.last})" ) )
(value >= 0 and value < 100)
(value >= 100 and value < 200)

groupedもいい

scala> List(1,2,3,4,5).grouped(2).toList
res34: List[List[Int]] = List(List(1, 2), List(3, 4), List(5))

ふつうのコトをふつうにこなす、コレクション(データ構造)を取り扱うためのアルゴリズムScalaちゃんは豊富、って感じ。(多分関数型言語全般)

ネストしたリストを均す

ほかにもやり方ありそうだけど。

scala> List(List(1,2)).fold(List())(_:::_)
res34: List[Int] = List(1, 2)

scala> List(List(1,2), List(3,4)).fold(List())(_:::_)
res35: List[Int] = List(1, 2, 3, 4)

これflattenでいいじゃん。。情弱さにしけた。

scala> List(List(1,2), List(3,4)).flatten
res1: List[Int] = List(1, 2, 3, 4)