LoginSignup
11
6

More than 1 year has passed since last update.

Kotlinのinfix

Last updated at Posted at 2020-07-01

これは何か

kotlinの予約語、infixってなんやねんって思っちゃったので定義や作り方を調べてみました。

目次

  • なんとかfix
  • 標準ライブラリの例:「to」
  • infixの関数を定義する
  • 定義したinfixの関数をKotlinから呼び出す
  • 定義したinfixの関数をJavaから呼び出す
  • infix関数を逆アセンブルする
  • 参考

なんとかfix

そもそも、infixって英単語に馴染みがないですよね。
試しにGoogle翻訳にかけると、「中置」って返ってくる。
ここで気づきます、なんとかfixの一つだ、と。他にもよく使ってるなんとかfixがありますよね、そういえばprefixとかsuffixとか...
中置きの文字通り、オブジェクトや値の間にinfix関数を置いて、挟まれた2つのオブジェクトや値を引数に処理を行う関数を定義できるKotlinの機能である。

標準ライブラリの例:「to」

標準ライブラリのinfix関数 to は、下記のように呼び出して Pair インスタンスを生成する。

Pair.kt
val pair: Pair<String, String> = "key" to "value"

メソッドは、下記のように拡張関数として定義されている。

Standard.kt
/**

 * Creates a tuple of type [Pair] from this and [that]. 
 * 
 * This can be useful for creating [Map] literals with less noise, for example: 
 * @sample samples.collections.Maps.Instantiation.mapFromPairs 
 */
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) 

参考:Tuples.kt

それでは、infix関数を自作してみたい。

infixの関数を定義する

よく遭遇するような、「同じようなオブジェクトを持っている、似ているオブジェクトのインスタンスに値を詰め替える」ケースを想定する。
ここでは、Customerクラスの値に履歴シーケンスをくっつけてCustomerHistoryクラスのインスタンスを生成し、それらのインスタンスをジェネリクスに含むPairインスタンスを返す関数を作る。

まずはインプットと合成の結果となるデータクラスを定義。

Data.kt
data class Customer (
        var id: String,
        var name: String
)

data class CustomerHistory (
        var id: String,
        var name: String,
        var seq: Int
)

元ネタであるCustomerから、シーケンス付きでCustomerHistoryインスタンスを生成するinfix関数を、以下のように定義する。

Infix.kt
/**
 * 拡張関数内部でレシーバをthisとして扱っている。シーケンスは関数の引数として受け取る。
 */
infix fun Customer.mapToHistory(seq: Int): Pair<Customer, CustomerHistory> = Pair(
        this,
        CustomerHistory(
                this.id,
                this.name,
                seq
        )
)

これで準備完了。さて、Kotlinコード、およびJavaコードから定義したinfix関数を呼んでみる。

定義したinfixの関数をKotlinから呼び出す

上記で定義した自作infix関数を呼び出す。

Caller.kt
// 元ネタ。
val customer: Customer = Customer("00001", "Eron Musk")

// 元ネタと履歴オブジェクトのPairをシーケンス付きで返す。
val pair: Pair<Customer, CustomerHistory> = customer mapToHistory 2

定義した関数が、例としてよくなかったのでありがたみがわかづらいけども、オブジェクトや値の間に定義した関数を置くことで結果を得られる。

定義したinfixの関数をJavaから呼び出す

Kotlinの相互運用性により、Javaからも呼び出しが可能である。
同じような呼び出しのスニペットを、Javaでもやってみると

Caller.java
/**
 * 戻り値は、Kotlinのライブラリに含まれるkotlin.Pairをimportして定義する。
 */
public static void printHistory(Customer customer) {
        Pair<Customer, CustomerHistory> customerHistory = InfixKt.mapToHistory(customer, 2);
}

やはりなんとなく予想はついてたけど、Javaからみるとinfix関数はstaticメソッドになっている。

infix関数を逆アセンブルする

定義したKotlinファイルはkotlincでコンパイルした。

Javaからの呼び出しを見ることで命令手続きは大方想像がつくんだけども、気になる。
ということで、コンパイルされたKotlinファイル(クラスファイル)を逆アセンブルしてみる。


mac:classes user$ javap InfixKt.class
Compiled from "Infix.kt"
public final class InfixKt {
  public static final kotlin.Pair<Customer, CustomerHistory> mapToHistory(Customer, int);
}

めちゃくちゃ想像どおりだった...クラスのない、もしくは複数クラスをもつKotlinファイルは、コンパイルされると<file name>Ktと命名される。もとのファイル名は Infix.ktだった。
逆アセンブルの結果を見ると、finalなクラスに内包されたstaticメソッドになっている。

2020-07-02:追記
staticメソッドになっているのは、自作した関数が拡張関数であるためでした。

javapにオプションをつけて、もう少し詳しい情報を出力してみる。

mac:classes user$ javap -c -l -p InfixKt.class
Compiled from "Infix.kt"
public final class InfixKt {
  public static final kotlin.Pair<Customer, CustomerHistory> mapToHistory(Customer, int);
    Code:
       0: aload_0
       1: ldc           #10                 // String $this$mapToHistory
       3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: new           #18                 // class kotlin/Pair
       9: dup
      10: aload_0
      11: new           #20                 // class CustomerHistory
      14: dup
      15: aload_0
      16: invokevirtual #26                 // Method Customer.getId:()Ljava/lang/String;
      19: aload_0
      20: invokevirtual #29                 // Method Customer.getName:()Ljava/lang/String;
      23: iload_1
      24: invokespecial #33                 // Method CustomerHistory."<init>":(Ljava/lang/String;Ljava/lang/String;I)V
      27: invokespecial #36                 // Method kotlin/Pair."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
      30: areturn
    LineNumberTable:
      line 12: 6
      line 13: 10
      line 14: 11
      line 15: 15
      line 16: 19
      line 17: 23
      line 14: 24
      line 12: 27
      line 19: 30
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      31     0 $this$mapToHistory   LCustomer;
          0      31     1   seq   I
}

やはり、コンパイルされたあとも見知ったJavaのオペコードになっているようである。

3行まとめ

  • infixとは、中置きを指す言葉で接頭辞/接尾辞の間に位置するようなもの。
  • infix関数は、値やオブジェクトの間に置いて処理を行う関数である。
  • Javaからみると、staticメソッドのシンタックスシュガーのようになっている。

参考

書籍
Kotlinイン・アクション

ウェブサイト
中置き記法 - Kotlinリファレンス

11
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6