Edited at

Kotlinで関数型プログラミング風

More than 1 year has passed since last update.

最近RubyからElixirに流れて、何でも関数で書きたい子供みたいな厄介な病気になってしまいました。

Kotlin触ってN日目程度なので何か思いつくたびに書き足していこうと思います


Recrucive

例えばこんなコード


Elixir

defmodule String do

def multiply(text, count) do
multiply(text, count, "")
end

def multiply(_text, 0, temp) do
temp
end

def multiply(text, count, temp) do
multiply(text, count - 1, temp <> text)
end
end

String.multiply("Hello", 10000)
# => "HelloHelloHelloHelloHe........


をKotlinで実現してみたい。


Kotlin

fun main(args: Array<String>) {

println("Hello!".multiply(2))
}

fun String.multiply(i: Int) : String {
return this.multiple(i - 1, this)
}

tailrec fun String.multiply(i: Int, temp: String) : String {
if (i <= 0) return temp
this.multiply(i - 1, temp + this)
}

// => "Hello!Hello!"


ところがこれ何故か大きすぎる i では java.lang.StackOverflowError を吐いて死にます。

公式ドキュメントには


This allows some algorithms that would normally be written using loops to instead be written using a recursive function, but without the risk of stack overflow.


って書いてあるのにな…

Javaにデコンパイルしたりしてみてみた


Javaにデコンパイル

import java.io.PrintStream;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;

// ...

public final class Recursive_testKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull((Object)args, (String)"args");
String string = Recursive_testKt.multiple$default("Hello!", 20000, null, 2, null);
System.out.println((Object)string);
}

@NotNull
public static final String multiple(@NotNull String $receiver, int i, @NotNull String temp) {
do {
Intrinsics.checkParameterIsNotNull((Object)$receiver, (String)"$receiver");
Intrinsics.checkParameterIsNotNull((Object)temp, (String)"temp");
if (i == 0) break;
temp = temp + $receiver;
--i;
} while (true);
return temp;
}
}


while になっている…なぜStack Overflowするのか

よく使う "string".repeat(実装)CharSequence で定義してあって、その内部実装では StringBuilder を使っていました。

と思ってこんなふうにしてみたんですが


Kotlin

fun main(args: Array<String>) {

println("Hello!".multiply(2000))
}

fun CharSequence.multiply(i:Int) : String {
return StringBuilder(this).multiply(i - 1, StringBuilder()).toString()
}

tailrec fun StringBuilder.multiply(i:Int, temp:StringBuilder) : StringBuilder {
return if (i <= 0) temp else this.multiply(i - 1, temp.append(this))
}


だめでした。頑張って調べよう…


Pipe Operator (Elixir や F# にあるあれ)

ないです

https://discuss.kotlinlang.org/t/pipe-forward-operator/2098/21

↑を参考にそれっぽくは書けます

fun main(args: Array<String>) {

"Hello"
.run { plus("World!") }
.run { repeat(2) }
.let { println(it) }
}

// => "HelloWorld!HelloWorld!"

続ける関数によって run let apply を使い分けなければならないのが面倒ですね


2017/5/24 追記

T.apply実装を見てみたところ


apply

/**

* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

となっていて、 副作用を起こしてもとのオブジェクトを返す (構造体でなければ返るものは渡したものと同じ)のでこの場合使えない気がします。つまり破壊的な作業を apply すれば良いのですがこの場合そんなことしないと思いますので…

となると apply は実質使わないのでは


リンク