SwiftでMonoidにトライしてみた

  • 3
    Like
  • 0
    Comment
More than 1 year has passed since last update.

元の資料は市ヶ谷Geek★Night #3で発表されていた「ループで遊ぼう」です。

Swift2.0でやりました。


前提

ProductItemCode の構造体かクラスを作る。 Productname を持っていれば何でもいい

struct Product {
    let id: Int
    let name: String
}

struct Item {
}

struct Code {
}

Product から Itemを、 StringItem から Code の配列を作る関数を用意しておく。これも作れさえすればなんでもいい

func ItemMake(product: Product) -> Item {
    return Item()
}
func CodesMake(name: String, item: Item) -> [Code] {
    return [Code()]
}

そして今回のお題に使うもとになる Product を適当に配列で用意して準備完了

let products = [
    Product(id: 0, name: "ぜろ"),
    Product(id: 1, name: "いち"),
    Product(id: 2, name: "に")
]

本題

for文あり

きもい

var allItemsBuf = [Item]()
var allCodesBuf = [Code]()

for product in products {
    let item = ItemMake(product)
    allItemsBuf.append(item)
    allCodesBuf += CodesMake(product.name, item: item)
}

let allItems = allItemsBuf
let allCodes = allCodesBuf

map

このくらいだったら標準のやつですぐにやれて良い。

let allItems = products.map(ItemMake)
let allCodes = products.flatMap{ p in
    CodesMake(p.name, item: ItemMake(p))
}

Monoid

Monoidの準備

struct Monoid<T> {
    let op: (T, T) -> T
    let zero: T
}

let sum = Monoid<Int>(op: +, zero: 0)
func concatMake<T>(type: T.Type) -> Monoid<Array<T>> {
    return Monoid<Array<T>>(op: +, zero: [])
}

func foldMap<T,U>(array: [T], mb: Monoid<U>)(f: T -> U) -> U {
    return array.reduce(mb.zero){ a, b in
        mb.op(a, f(b))
    }
}

concat については sum と同様 let concat = ... みたいにやりたかったけど、そうすると Array<T>T を指定できなくなったので誤魔化した。もう少し調べたらやり方出てくるのかな?

元のScalaの資料では foldMapmb: Monoid<U>implicit で省略させる話が出てるけどSwiftでのやり方がよくわからないのでナシ。 protocol Monoid 作れば mb がなくても zeroop を呼び出せるけどそうすると後で出てくるtuple版の書き方がよくわからなくなる…。

Monoid(まだ無駄が残る版)

let allItems = foldMap(products, mb: concatMake(Item))(f: {p in [ItemMake(p)]})
let allCodes = foldMap(products, mb: concatMake(Code))(f: {p in CodesMake(p.name, item: ItemMake(p))})

Monoid(tuple使用版)

func tuple2<T,U>(ma: Monoid<T>, mb: Monoid<U>) -> Monoid<(T,U)> {
    return Monoid<(T,U)>(
        op: {(x: (T,U), y: (T,U)) -> (T,U) in
            (ma.op(x.0, y.0), mb.op(x.1, y.1))
        },
        zero: (ma.zero, mb.zero)
    )
}

let (allItems, allCodes) = foldMap(products, mb: tuple2(concatMake(Item), mb: concatMake(Code)))(f: {p in
            let item = ItemMake(p)
            return ([item], CodesMake(p.name, item: item))
        }
    )

Swiftだとtupleが0はじまりでfoldLeftがreduceでもうよくわからない


以上です。

mb を省略する手段と Monoid<T>Monoid<Array<T>> をどうにかしたかったです。難しいです。