javascriptとSwiftでArray#reduceを自分でも実装してみた話

最近1年くらいQiitaにばかり書いていましたが、こちらではもう少し日記みたいな感じで徒然と書いていこうと思います。

さて、今日はちょっと趣味でjavascriptの勉強をしようと思いました。
これまでjsは真面目に書いてこなかったのですが、とりあえずとっかかりにと思ってfunctional-javascript-workshopというのをやってみています。

Functional Javascript

まだ18 challenges中7個目ですが、既にreduceを再帰で実装しましょうという課題が出てきました。
こういうのやってると新人研修を思い出します。
私の場合回答はこんな感じになりました。

/* @flow */
module.exports = function reduce(arr:Array<any>, fn:any, initial:any):any {
  var reduceOneFn:any = function reduceOne(index:number, value:any):any {
    if (index > arr.length - 1) return value
    return reduceOne(index + 1, fn(value, arr[index], index, arr))
  }
  return reduceOneFn(0, initial)
}

せっかくなのでFlowtypeで型チェックをしながらやっています。ちなみにGitHubが出しているatomというエディタだとFlowtypeのpackageをいれればリアルタイムで型チェックをさせられるので便利でした。
上の例だとanyばっかりであまり型チェックが意味をなさなくなってますが。。
ジェネリクスを使ってFlowtypeに型チェックをさせようとするとなんだかうまくいかなかったんです。

なんだかモヤッとしたので、型に厳格な例の言語でやるとどんな感じになるのか、やってみました。

Swift

extension Array {
  func reduceAFn<U>(index:Int, value:U, combine:((U, T, Int) -> U)) -> U {
    if index > self.count - 1 { return value }
    return reduceAFn(index + 1, value: combine(value, self[index], index), combine: combine)
  }
  func reduceA<U>(combine:((U, T, Int) -> U), initial:U) -> U {
    return reduceAFn(0, value: initial, combine: combine)
  }
}

ジェネリクス使えるよ。そう、Swiftならね。

SwiftのArrayの宣言部分ではすでに型引数 T が宣言されているので、extension内でもそのまま使うことができました。
他のところは自分で U という型引数を定義しています。
なおSwiftではローカル関数内でローカル関数自身を呼ぶことができないという制限があり、reduceAFnを外だしせざるを得ませんでした。

ユースケースとしてはこんな感じですね。

// 配列の中身を全部足す
var r = [0,1,2,3,4,5].reduceA({(prev, current, index) in return prev + current}, initial: 0)
println(r) // 15

// 単語の出現回数を数える
var r1:[String:Int] = ["hello", "hello", "yellow", "yellow", "yellow", "foobar"].reduceA({(prev, current, index) in
  var countMap:[String:Int] = prevv
  countMap[current] = (countMap[current] ?? 0) + 1
  return countMap
}, initial: [String:Int]())
println(r1) // [hello:2, yellow:3, foobar:1]

再帰的な書き方って、人に説明するのもされて理解するのも難しいんですが、問題が解けたときの満足感はなにものにも代えがたいものがありますね。

ひとまず眠いし今日はこんなもんで。