LangExt

これは、LangExtの概要を説明するドキュメントです。

用語

このドキュメントおよびLangExtで使用する用語の説明です。

関数

staticメソッドやラムダ式、デリゲートを単に「関数」と記述しています。 他にも、関数を引数に取るようなメソッドや、関数を返すようなメソッドのことを「高階関数」と記述します。

モジュール

staticクラスのことをモジュールと記述しています。

LangExtが提供するジェネリック型に対しては、同名のモジュールに拡張メソッドを含む関数を定義します。 例えば、ジェネリック型のOption型に対するモジュールの名前はOptionです。
それに対して、LangExtが提供する非ジェネリック型には、型名の後ろに「Module」というサフィックスを付けたモジュール名を使います。 例えば、非ジェネリック型のUnit型に対するモジュールの名前はUnitModuleです。

LangExt以外で提供される型に対する関数の定義は、ジェネリック型と同名の非ジェネリック型を持たない型と、それ以外の型で扱いが異なります。 ジェネリック型と同名の非ジェネリック型を持たない型の場合、ジェネリック型と同名のモジュールに拡張メソッドを含む関数を定義します。 例えば、System.Funcなどがこれに該当し、Funcモジュールに関数を定義しています。 それに対して、ジェネリック型と同名の非ジェネリックを持つ型、あるいは非ジェネリック型の場合、 型名の後ろに「Module」というサフィックスを付けたモジュール名を使います。 例えば、System.Tupleなどがこれに該当し、TupleModuleモジュールに関数を定義しています。

型の定義場所 型パラメータ 型パラメータなしの型の有無 モジュール名
LangExt - 型名と同名
LangExt - 型名 + Module
LangExt以外 型名 + Module
LangExt以外 型名と同名
LangExt以外 - 型名 + Module

また、インターフェイスに対するモジュールの場合、Iプレフィックスを取り除いた名前を使います。 例えば、System.Collections.Generic.IEnumerableなどがこれに該当し、Enumerableモジュールに関数を定義しています。

モジュール以外のクラスや、構造体、列挙型などをまとめて、型と記述しています。

シーケンス

Seq[T]のことを、シーケンスと呼びます。 LangExtではLINQ to Objectsを捨て、シーケンスに対して「より関数プログラミングの語彙に近いAPI」を再構築しています。 その際に、IEnumerable[T]ではなく、Seq[T]を操作の対象に選んだため、配列との統一的なAPIは諦めています。 しかし、オーバーロードを極力排除したことにより、LINQ to Objectsよりも拡張の幅が大きくなっています。 また、LINQ to Objectsより多くの操作を提供しているため、LINQ to Objectでは実現できなかった表現力を備えています。

表記

このドキュメントおよびLangExtで使用する表記の説明です。

関数の型

関数の型は、

(int, int) → int

のように、引数の型と戻り値の型をで区切った形式で記述します。 上の例は、intを2つ受け取ってintを返す関数です。 引数が1つの場合は、引数を囲むかっこを省略します。

Funcデリゲートの型も同様に、引数の型と戻り値の型をで区切って表しますが、全体をかっこで囲みます。

(intint)

これは、intを受け取ってintを返すFuncデリゲートを表します。 Actionデリゲートは、戻り値の型としてvoidを指定します。 例えば(int → void)は、intを受け取るActionデリゲートを表します。

ジェネリック型

ジェネリック型は、

Option[T]

のように角かっこを用います。 これは、ドキュメンテーションコメントの可読性を考慮した結果です。

型パラメータ

型パラメータは、Tから始まる一文字の大文字(T, U, V, ...)で記述します。 例えば、Tを受け取ってTを返す関数は、

T → T

です。 これに続けて、番号を付ける場合もあります(T1, T2, ...)。

型パラメータに明確な意味があるような場合、Tに続けて意味を表す語を続けます。 例えば、成功の場合の型をTSuccessとして表し、失敗の場合の型をTFailureとして表したりします。

タプル

タプルの型は、各要素の型をアスタリスクで連結して記述します。 例えば、T1とT2のタプルの型は、

T1 * T2

です。 タプルを受け取る関数(タプル関数)と複数引数の関数は、以下のように区別されます。

T1 * T2 → U
(T1, T2) → U

Choice

Choice(択一)の型は、各要素の型を+で連結して記述します。 例えば、T1とT2のChoiceの型は、

T1 + T2

です。

シーケンス

波かっこの中にカンマ区切りで要素を列挙することで、シーケンスの値を表します。 例えば、要素として1~3の整数を含むシーケンスは、

{ 1, 2, 3 }

と表記します。

LangExtが提供する主な型とモジュール

LangExtが提供する主な型とモジュールについて、その役割や意味を簡単に説明します。 実際の使い方に関しては、ドキュメンテーションコメント等を参考にしてください。

Placeholder型

Placeholder型は、プレースホルダーとして使用されることを前提とした型です。

この型をユーザが明示的に使用することはありません。 この型は、Option型を型パラメータを指定せずに生成したりするために使用されます。 基本的には暗黙の型変換のために使用されますが、具体的な使用例はOption型などを参照してください。

この型の値に意味はないため、列挙子を持たない列挙型として実装されています。 数値をキャストすることでこの型の値を作ることはできますが、そうした場合の動作は保証しませんので注意してください。

Unit型

Unit型は、意味を持つ値がないことを表すための型です(0bitの情報を持つ型とも言えるでしょう)。 この型はフィールドを一つも持たない構造体として実装されています。

voidとの違いは、voidは値がないうえに、型パラメータとして使用することができないのに対して、Unit型はそうではないという点です。 値がない場合とある場合を共通化したい場合、Unit型が使用できます。

Ignore関数を使うことによって、任意の型の値をUnit型に変換する(捨てる)ことが可能です。

Option型

Option型は、失敗しうる計算の結果として使用できる型です。 この型は、nullを使うよりも安全に「値がないこと」を表せます。

nullはコンパイラによってnullチェックを強制できないため、プログラマが必要な場所にnullチェックを埋め込む必要があります。 これに漏れがあると、NullReferenceExceptionが発生するバグとなります。 この問題の根本には、nullが入りうる型Tの値tに対して、nullが入っていようがいまいが関係なくTの持つメンバーにアクセスできてしまうという問題があります。

int F(string str)
{
    // strにnullが入っていてもいなくても、strのメソッドは呼べてしまう
    // (少なくとも、コンパイルが通ってしまう)
    return str.IndexOf("a");
}

それに対して、Option型はTをラップする型なので、Tの持つメンバーに直接アクセスすることは出来ません。

Option<int> F(Option<string> opt)
{
    // optはstringではないため、stringのメソッドであるIndexOfは呼び出せない
    //return opt.IndexOf("a");
    // Mapに渡す関数は、値がある場合(Someの場合)だけ呼び出され、strはnullではないことが保証されている
    // 値がない場合(Noneの場合)は、何も行わずにそのままNoneが返される
    return opt.Map(str => str.IndexOf("a"));
}

いったんOption型でラップしたものからOption型をはぎ取るためには、「値がない場合」の考慮をAPIによって強制されます。

var resOpt = F(opt); // 先ほどのFを呼び出す
// MatchでOptionをはぎ取ることができるが・・・
var res = resOpt.Match(
    Some: v => v,    // 値がある場合と
    None: () => -1); // ない場合を考慮する必要がある

// GetOrでも中の値を取得できるが、やはり値がなかった場合の考慮が必要
//var res = resOpt.GetOr(-1);

このように、Option型はnullよりも安全に「値がない」ことを扱えます1

nullを使うのではなく、契約プログラミングによって(Code Contractなどを使って)nullを排除する方向はどうでしょうか。 これはこれで重要なことではあるのですが、値がないことを表す必要が出た場合にどうするのか、という問題が起きます。 その際に、nullではなく、Option型を使えばいいのです。

値がない可能性の一番身近な例としては、文字列のパースがあります。 例えば、int.Parseはintとして不正な文字列が渡されると、例外を投げます。 これを避けるために、出力引数を使ったバージョンであるint.TryParseが用意されていますが、 出力引数は結果格納用が必要で、使うのは面倒です。

int result;
if (int.TryParse(str, out result))
{
    // Parseに成功した場合の処理
}
else
{
    // Parseに失敗した場合の処理
}

これに対してOption型は、その必要がありません。

// LangExtにはTryToIntは用意されていないが、簡単に定義できる
str.TryToInt().Match(
    Some: result => /* 変換に成功した場合の式 */,
    None: () => /* 変換に失敗した場合の式 */);

boolを返す出力引数を取る関数が使いたくなった場合は、立ち止まってOption型で置き換えれないかを考えましょう。 出力引数が1つしかない場合は簡単に置き換え可能です。 出力引数が複数ある場合、特にstring型やint型といった基本的な型を複数出力引数に指定するのはアンチパターンなので、 そういう場合にはそれらをまとめた型を作りましょう。 そうすれば、Option型で置き換え可能になります。

Optionの生成

Option型の値を生成するためには、Optionモジュールの関数・プロパティを使用します。 Option.Some/Option.None以外に、Option.Createという関数により、nullの場合はNoneとして、それ以外の場合はSomeとしてOptionオブジェクトを生成できます。

クエリ式

Option型はクエリ式で使うこともできます。

from a in F1() // Option[int]を返す関数F1
from b in F2() // Option[int]を返す関数F2
from c in F3() // Option[string]を返す関数F3
select c + (a + b)

F1の結果を捨てる場合、&&演算子を使うことで余分な変数名の導入を避けることでできます。

// F1の結果は捨て、F2の結果のみ使う
from a in F1() && F2()
from c in F3()
select c + a

ただし、これができるのは&&の両辺の型が同じである時のみです。 もし型が違う場合は、AndThenメソッドが使えます。

// F1の結果は捨て、F3の結果のみ使う
from c in F1().AndThen(() => F3()) // もしくは単に F1().AndThen(F3)
from b in F2()
select c + b

これらのイディオムは、クエリ式内で値を捨てることができないC#では重要です。

クエリ式は、「一つでも失敗したら全体として直ちに失敗する」ことを表現しますが、||演算子を使うことで「一つでも成功したら全体として直ちに成功する」ことも表現できます。

return F1() || F2() || F3().Map(int.Parse);

この場合、すべての戻り値の型が同じである必要があります。

null合体演算子のオーバーロードが可能であれば、GetOrElseメソッドの代わりにそちらを使うことができるようになるのですが、 現在のC#はこれを許していないため、GetOrElseメソッドを使用する必要があります。 また、null合体演算子の連続opt1 ?? opt2 ?? opt3 ?? defaultValueを実現するためには、(opt1 || opt2 || opt3).GetOrElse(() => defaultValue)のように記述する必要があります。 defaultValue部分が単純な値の場合(生成コストが低く、副作用を起こすような計算ではない場合)、GetOrElse(() => defaultValue)の代わりに、GetOr(defaultValue)としてもいいでしょう。

Result型

Option型は、失敗の原因を保持することができません。 Result型では、失敗の原因も保持することができます。

Option型同様、クエリ式の対象にすることもできますが、Option型では対応していたwhereには対応していません。 また、失敗側の型は統一する必要があります。

Failureの操作

Result型はOption型と違い、失敗の原因を保持することができます。 失敗側の値に対する操作を行いたい場合もあるため、これに対応しています。 失敗側の値に対して操作を行いたい場合は、成功側の操作に、サフィックス「Failure」を付けます。

例えば、失敗側の結果に対してMapが行いたい場合は、

res.MapFailure(e => e.ToString());

のように記述します。

失敗側に対してクエリ式が使いたい場合は、SwapResultを呼び出すことで成功と失敗を入れ替えることで一応実現できます。 クエリ式による操作が終わったら、再びSwapResultを呼び出してください。

Seq型/Seqモジュール

Seq[T]は、Tのシーケンスを表します。

関数を引数に渡す高階関数のうち、インデックスを取るバージョンはWithIndexで終わります。 例えば、MapWithIndexは(T, int) → Uという関数を受け取り、第二引数に現在の要素のインデックスが渡されてきます。

失敗する可能性のある関数2に対しては、Optionを返すバージョンを提供しています。 Optionを返すバージョンの関数は、Tryから始まります。 例えば、Findは要素が見つからなかった場合に例外を投げますが、TryFindはNoneを返します。

Optionを含むシーケンスの変換

Optionのシーケンスに対する2つの関数、SequeneとOnlySomeを提供しています。

Sequenceは、OptionのシーケンスがSomeのみを含む場合にSomeをシーケンスの外側にくくり出します。 一つでもNoneが含まれていた場合、Noneとなります。 例えば、{ Some(1), Some(2), Some(3) }とあった場合、Some { 1, 2, 3 }となり、 { Some(1), Some(2), None }とあった場合、Noneになります。

OnlySomeは、OptionのシーケンスからSomeの要素のみを集め、Someを取り除きます。 例えば、{ Some(1), Some(2), None, Some(3) }とあった場合、{ 1, 2, 3 }となります。

Resultを含むシーケンスの変換

Optionを含むシーケンスの変換同様の操作が、Resultに対しても提供されています。 Sequenceに対してはSequenceSuccess/SequenceFailureが、OnlySomeに対してはOnlySuccess/OnlyFailureがそれぞれ対応します。

nullを含みうるシーケンスの変換

MapOption関数を使うことで、nullを含みうるシーケンスを、Optionのシーケンスに変換できます。

参照型の場合はSeq[T] → Seq[Option[T]]ですが、null許容型の場合Seq[T?] → Seq[Option[T]]と、 null許容型が取り除かれることに注意してください。

同様に、MapResult関数を使うことで、nullを含みうるシーケンスをResultのシーケンスに変換できます。 この際、nullの場合はResult.Failure(Unit)に変換されます。

クエリ式の提供

シーケンスはクエリ式も提供しています。 LINQ to Objectsが提供しているクエリ式はすべて使用可能です。

IEnumerableについて

IEnumerable[T]に対してもいくつか拡張メソッドを定義していますが、シーケンスに比べると限定的です。

基本的には、IEnumerable[T]は使わず、Seq[T]を使います。 ToSeqメソッドによって、IEnumerable[T]をシーケンスに変換できます。

Choice型

Choice型は、択一を表す型です。 Result型に似ていますが、Result型が「成功」と「失敗」という風に各型パラメータに意味を与えているのに対して、 Choice型は各型パラメータを平等に扱います。 そのため、Result型よりもより抽象度の高い型と言えます。 また、現状では16個までの型パラメータを持つことが出来ます。

Choice型に対して可能な操作はあまりありません。 基本的にはMatchメソッドを使うことになります。

Choice<int, string> c = ...
c.Match(
  i => ...,
  str => ...);

Createモジュール

Createモジュールは、LangExtやC#の標準ライブラリで扱うことのできる様々な型の値を生成するための関数を提供します。

基本的には、モジュール名.Createというメソッドを提供している場合、Create.型名という関数を提供しています。 例えば、Seq.Createに対してCreate.Seqを、TupleModule.Createに対してCreate.Tupleを提供しています。 この例からわかるように、Createモジュールの関数を使った方が統一性のある記述ができますので、CreateメソッドではなくCreateモジュールの使用をお勧めします。

TupleModuleモジュール

TupleModuleモジュールは、C#標準のタプルを使いやすくするための拡張メソッドを提供します。

C#標準のタプルは、7要素までしか自然に扱うことができませんが、TupleModuleモジュールを使うことで型の記述以外は16要素まで自然に扱うことができるようになります。 例えば、標準ライブラリでは16要素タプルの15要素目にアクセスするためにはtpl.Rest.Rest.Item1と記述する必要がありますが、LangExtを使うとtpl._15()と書けます。 n要素タプルには_1(), _2(), ..., _n()までの拡張メソッドのほか、1番目と2番目の要素に対しては、Fst()Snd()という拡張メソッドも用意しています。

標準ライブラリの範囲では、生成も9要素以上はコンストラクタを使う必要があるため、非常に面倒です。

var tpl = new Tuple<T1, T2, ..., T7, Tuple<T8, T9, ..., T14, Tuple<T15, T16>>>(
    t1, t2, ..., t7, new Tuple<T8, T9, ..., T14, Tuple<T15, T16>>(
        t8, t9, ..., t14, Tuple.Create(t15, t16)));

LangExtでは、これもとても簡単に記述できます。

// TupleModule.Createでも可
var tpl = Create.Tuple(t1, t2, ..., t16);

ほかにも、タプルの擬似的なパターンマッチや、指定要素に対するMapなどが可能です。

C#ではそもそもタプルを多用すべきではありません(無名型が使えるのであればそちらを使うといいでしょう)。 このモジュールは自動生成や、次に説明するFuncモジュールのために実装されています。 このモジュールで扱えるタプルの要素数が16までなのは、標準のFuncデリゲートが16引数までしか対応していないためです。

Funcモジュール

Funcモジュールは、Funcデリゲートの機能を強化する関数を提供します。

このモジュールには、複数引数の関数「(T1, ..., Tn) → U」、タプル関数「T1 * ... * Tn → U」、カリー化関数「T1 → ... → Tn → U」の相互変換を行う関数を提供しています。 以降ではT1とT2を受け取ってUを返す関数のみ記載していますが、T16まですべて対応しています。

複数引数関数とカリー化関数の相互変換

複数引数関数をカリー化関数に変換する関数((T1, T2) → U) → (T1 → T2 → U)を、Curryという名前で提供しています。拡張メソッド版に、Curriedも提供しています。 これの逆操作(T1 → T2 → U) → ((T1, T2) → U)を、Uncurryという名前で提供し、拡張メソッド版としてUncurriedも提供しています。

CurryしてUncurryすると、意味としては何もしていないのと同じです。

タプル関数とカリー化関数の相互変換

タプル関数をカリー化関数に変換する関数(T1 * T2 → U) → (T1 → T2 → U)を、CurryXという名前で提供しています。拡張メソッド版に、CurriedXも提供しています。 これの逆操作(T1 → T2 → U) → (T1 * T2 → U)を、UncurryXという名前で提供し、拡張メソッド版としてUncurriedXも提供しています。 サフィックスとして使われているXは、タプルを表すアスタリスクが由来です3

CurryXしてUncurryXすると、意味としては何もしていないのと同じです。

複数引数関数とタプル関数の相互変換

複数引数関数をタプル関数に変換する関数((T1, T2) → U) → (T1 * T2 → U)を、Tupleという名前で提供しています。拡張メソッド版に、Tupledも提供しています。 これの逆操作(T1 * T2 → U) → ((T1, T2) → U)を、Untupleという名前で提供し、拡張メソッドとしてUntupledも提供しています。

TupleしてUntupleすると、意味としては何もしていないのと同じです。

Option型を返す関数への変換

以降では、Tを受け取ってUを返す関数のみ記載していますが、 T16までの複数引数関数すべてに対応しています(カリー化関数やタプル関数には対応していないので、これらの関数に使いたい場合は、変換関数で複数引数関数に変換する必要があります)。

nullを返しうる関数T → Uを、T → Option[U]に変換する関数を、NullToOptionFuncという名前で提供しています。 この関数は、渡された関数の結果がnullだった場合にNoneとして返し、それ以外の場合はSomeでその値を包んで返します。 Uにclass制約が付いた形で提供されるほか、Uにstruct制約が付いた形でT → U?に対して、T → Option[U]に変換するバージョンも提供しています。 後者の場合、null許容型U?がnullを許容しない型Option[U]に変化する点に注意してください(nullはNoneで表されるため、不要になります)。

例外を投げうる関数T → Uを、T → Option[U]に変換する関数を、ExnToOptionFuncという名前で提供しています。ExnはF#(もしくはOCaml)由来の短縮です。 この関数は、渡された関数が例外を投げた場合にNoneとして返し、それ以外の場合はSomeでその値を包んで返します。

上記2つを同時に行う関数を、ToOptionFuncという名前で提供しています。

これらの逆操作は提供していません。

Result型を返す関数への変換

以降では、Tを受け取ってUを返す関数のみ記載していますが、 T16までの複数引数関数すべてに対応しています(カリー化関数やタプル関数には対応していないので、これらの関数に使いたい場合は、変換関数で複数引数関数に変換する必要があります)。

nullを返しうる関数T → Uを、T → Result[U, Unit]に変換する関数を、NullToResultFuncという名前で提供しています。 この関数は、渡された関数の結果がnullだった場合にFailureとして返し、それ以外の場合はSuccessでその値を包んで返します。 Uにclass制約が付いた形で提供されるほか、Uにstruct制約が付いた形でT → U?に対して、T → Result[U, Unit]に変換するバージョンも提供しています。 後者の場合、null許容型U?がnullを許容しない型Result[U, Unit]に変化する点に注意してください(nullはFailureで表されるため、不要になります)。

例外を投げうる関数T → Uを、T → Result[U, Exception]に変換する関数を、ExnToResultFuncという名前で提供しています。ExnはF#(もしくはOCaml)由来の短縮です。 この関数は、渡された関数が例外を投げた場合にFailureで例外を包んで返し、それ以外の場合はSuccessでその値を包んで返します。

上記2つを同時に行う関数を、ToResultFuncという名前で提供しています。 nullを返したことを表現するために、NullResultExceptionという名前の例外クラスを使っています。

これらの逆操作は提供していません。

関数型スタイルの関数から、OOPスタイルの関数への変換と、その逆

以降では、T1, T2を受け取ってUを返す関数のみ記載していますが、 T16までの複数引数関数すべてに対応しています。

多くのオブジェクト指向プログラミング言語では、ヘルパメソッドの第一引数にレシーバ相当のオブジェクトを置くスタイルがよく使われます。

public static class Objective
{
    public static Hoge F(Piyo self, Foo arg1, Bar arg2) ...
}

C#の拡張メソッドは、第一引数に「this」を付けることでまさに第一引数をレシーバのように扱う記述が可能になる機能です。

それに対して、関数プログラミングでは、関数をカリー化した上で最後の引数にレシーバ相当のオブジェクトを置くスタイルがよく使われます。

public static class Functional
{
    public static readonly Func<Foo, Func<Bar, Func<Piyo, Foo>>> F = arg1 => arg2 => self => ...
}

このスタイルではレシーバ相当のオブジェクトを一番最後に指定できるため、他の(固定的な)引数をあらかじめ与えておく、という方法が取れます。

var f = Functional.F(arg1)(arg2);
...
var res = f(self);

リストなどでは、レシーバ以外の引数はレシーバよりも固定的であることが多くあるため、非常に便利です。

しかし、これら2つのスタイルを常に用意しておくのは現実的ではありません。 そのため、LangExtではこれらのスタイルを相互変換するために、ToFunctionalとToObjectiveという関数を用意しています。

OOPLスタイルの関数Fに対して、ToFunctionalを呼び出すと、カリー化しつつ第一引数を最後に持って行った関数に変換できます。

var res1 = Objective.F(self, arg1, arg2);

var f = Objective.F.ToFunctional();
var res2 = f(arg1)(arg2)(self);

関数プログラミングスタイルの関数Fに対して、ToObjectiveを呼び出すと、アンカリー化しつつ最後の引数を最初に持って行った関数に変換できます。

var res1 = Functional.F(arg1)(arg2)(self);

var f = Functional.F.ToObjective();
var res2 = f(self, arg1, arg2);

ToFunctionalしてToObjectiveする、もしくはToObjectiveしてToFunctionalすると、元の関数に戻ります。

Applyモジュール

Applyモジュールは、OptionやResultに対して関数を適用できる機能を提供します。

通常、OptionやResultに対して関数を適用するには、Bind関数やクエリ式を使用する必要があります。 これは、束縛を含む複雑な関数適用には便利ですが、単に(T → U)からM[T] → M[U]に変換が必要になるだけの関数適用にはオーバースペックです。 そのような単純な関数適用には、Applyモジュールを使用します。

次のサンプルは複数のOption(o1, o2, ..., o16)に対してfuncを適用しています。

Apply.To(o1, o2, ..., o16).By(func)

funcの型は、To関数に渡される引数の型によって決定されます。 例えば、To関数に渡される引数の型が(Option[string], Option[int])だった場合、By関数に渡す関数の型は(string, int) → Tになります。

Result[T, U]のように複数の型引数が存在する場合、To関数に渡すResult[T, U]の型パラメータUは、すべてが同一である必要があります。


  1. Match関数で名前付き引数を使うアイディアは、ころくんのつぶやきから借りています。

  2. 失敗する可能性のある関数は、LangExt.Unsafe名前空間以下で定義しています。 そのため、これらを使用する場合はLangExt.Unsafe名前空間をusingする必要があります。

  3. サフィックスのXがタプルを表すアスタリスクから来ている、というのは、こじつけの理由で、これに至るまでに紆余曲折を経ています。 当初は、(T1 * T2 → U) → (T1 → T2 → U)をCurry、(T1 → T2 → U) → (T1 * T2 → U)をUncurryにしようとしていましたが、 Curryは引数の型の違うオーバーロードとして実現できるものの、Uncurryは戻り値の型が違うだけになるため、別の名前が必要でした。

    これにUncurry2と名前を付けてしまうと、紛らわしい(2引数版、2要素タプル版と勘違いしてしまう)という懸念があり、早々に却下されました。 Tupleという名前も考慮しましたが、これでは対称性が取れないという別の問題が生じます。 他の似たようなメソッドはCurryに対してUncurry、Tupleに対してUntupleとなっているため、ここだけCurryに対してTupleを採用するのは統一性がありません。

    そこで、CurryとUncurryを基本に、意味のない語を付ける案に至りました。 候補としてあがったものは、XやZ、アンダースコアなどです。 Xを選んだのは、Xがタプルを表すアスタリスクに見えるから、という由来として挙げた理由からです。

    Xを選んだものの、プレフィックスにするかサフィックスにするかは迷いました。 正直、こじつけで理由はあるものの、意味のない語であるため、どうでもよかったのです。 ただ、Xの後に母音が来ると、そのまま読めてしまうのが微妙な感じがしたので、Xuncurry(残カレー?)ではなく、UncurryXの方が良さそうでした。 こういう経緯を経て、タプル関数とカリー化関数の相互変換関数の名前がCurryXとUncurryXに決まりました。