KotlinでC#のLINQっぽいのを実装してみた


どうも最近Kotlinを使い始めたJava(Android)からの移行者です
普段はC#使ってます

C#のLINQ便利でいいですよね!
KotlinにもfilterやmapなどJavaのStreamライクなメソッドはありますがfilterなどのソースを見に行くとどうも遅延実行していないっぽいのです

追記)遅延実行するリスト操作関数としてSequenceとその拡張関数が用意されてました。すいません(>_<)
Sequence作成にはasSequence()関数を呼べばいいみたいです、なのでLINQライクな関数名がいいという方向けの記事になります

ということで自作してみました!

ちなみに遅延評価とよく聞くのですが
遅延評価いうなキャンペーンとかどうか - ぐるぐる~
によるとこの種の動作は遅延評価と表現するのは正しくないようです
ではどう表現するのか?となるのでC#のLINQ Whereのリファレンスを見に行くと

This method is implemented by using deferred execution.
このメソッドは遅延実行を使用して実装されます。

と表記されているので、ここでは遅延実行と表現します

注意

  • 勉強足らずなので内容には間違いがあるかもしれません
  • 関数とメソッドがごちゃまぜになっています
    • なぜKotlinは関数なのか
  • 圧倒的ドキュメント、コメント不足の内容になっています
  • テストコードが不足しています
  • mavenとかgradleよくわかっていない人が作ってます←おいw
  • stdlibは1.0.0-rc-1036です

KLinq

ほぼC#のLINQと同じ名前、引数で遅延実行を実装しているライブラリです

ソース

ソースはGithubで公開しています

build.gradle
repositories {
    mavenCentral()
    maven { url "https://raw.github.com/MeilCli/KLinq/master/klinq/repository" }
}

dependencies {
    compile 'meilcli:klinq:1.2'
}

で利用できるはずです

使い方

import com.twitter.meil_mitu.klinq.*

お決まりのおまじない()

var linq  = arrayOf(1,2).toEnumerable()
linq.where{ x -> x>0}...

toEnumerable()以外だいたいC#のと同じ感覚でイケるはずです
(上のは分離させてるけどもちろんメソッドチェーンできます)
...あれ、この場合は関数じゃなくてメソッドでよかったのかな....

Enumerable.range(0,10)
(0..9).toEnumeable()

よく使うrangeもありますが(start..end).toEnumeable()のほうが分かりやすいかもしれません
他にもEnumerable.repeat,Enumerable.emptyがあります

比較

機能 LINQ(C#) KLinq(Kotlin)
要素の取得(単一)    
  ElementAt elementAt
  ElementAtOrDefault elementAtOrDefault
  First first
  FirstOrDefault firstOrDefault
  Last last
  LastOrDefault lastOrDefault
  Single single
  SingleOrDefault singleOrDefault
要素の取得(複数)    
  Where where
  Distinct distinct
  Skip skip
  SkipWhile skipWhile
  Take take
  TakeWhile takeWhile
集計    
  Max max
  Min min
  Average average
  Sum sum
  Count count
  Aggregate aggregate
判定    
  All all
  Any any
  Contains contains
  SequenceEqual sequenceEqual
集合    
  Union union
  Except except
  Intersect intersect
ソート    
  OrderBy orderBy
  OrderByDescending orderByDescending
  ThenBy thenBy
  ThenByDescending thenByDescending
  Reverse reverse
射影    
  Select select
  SelectMany selectMany
  GroupBy groupBy
結合    
  Join join
  GroupJoin groupJoin
  Concat concat
  DefaultIfEmpty defaultIfEmpty
  Zip zip
変換    
  OfType ofType
  Cast cast
  ToArray toArray
  ToDictionary toDictionary
  ToList toList
  ToLookup toLookup
  AsEnumerable asEnumerable
よくやる奴    
    forEach

ToDo

  • ArrayかIterableに対して.toEnumeable()を呼ばなければいけない
    • 関数の名前がstdlibのと衝突する場合があるので名前を変えずに実装したかったのでこの対処法になりました
    • 追記)SequenceもasSequence()関数を用意してるあたりこうするしかないようですね
  • KotlinにはC#のdefault(T)がないのでxxxOrDefault関数などで規定値nullになる
    • TがNumberを継承しているかどうかさえ見ればなんとかなる気がしないこともないけどむりそう
  • average関数でJVMだとジェネリクスのオーバーロードで衝突するのでNumberを利用
  • sum関数でJVMだとジェネリクスのオーバーロードで衝突するのでNumberを利用→返り値がすべてDoubleになった
  • ofType,cast関数、いい実装方法が思いつかなかった
    • 追記)reifiedでそれっぽく対応
  • deferredの実装がいいのだろうけど一部lazyの実装してしまった
  • 関数のオーバーロードにデフォルト引数を多用したけどIntellij IDEAの補完的に普通にオーバーロードしたほうがいいかもしれない

参考