【2020-09-17追記】記述例一覧版を『カリー化チートシート』とし,本記事は現タイトルとしました.
【2020-08-14追記】『記述例一覧版』の記事を作成しました.
拙作『不動点コンビネータを用いた無名再帰関数の実行まとめ』の補足説明として書き始めたところ,カリー化関数を記述・利用するための独立した記事とした方が少なくとも約1名(自分自身)には役立ちそうだったので,新しく記事にした.なお,カリー化してくれる関数の定義ではないことに注意.複数言語にわたっている都合上,カリー化の解説部分を含め,各言語・パラダイムに精通している方々のツッコミ歓迎.
記法のみの一覧は次の通り.f
は関数,a
は引数を指す.
言語 | 引数の変更 | 引数の指定 | 備考 |
---|---|---|---|
Haskell | 空白区切り | f a a ・・・a |
|
Scheme |
lambda による無名関数 |
(・・・((f a) a)・・・a) |
|
Python | 関数内で定義した関数 | f(a)(a)・・・(a) |
無名関数も使用可 |
Ruby |
-> 等を用いた無名関数 |
f.(a).(a)・・・.(a) またはf[a][a]・・・[a]
|
カリー化メソッドあり |
JavaScript |
=> やfunction を用いた無名関数 |
f(a)(a)・・・(a) |
|
Scala |
=> を用いた無名関数 |
f(a)(a)・・・(a) |
カリー化メソッドあり,静的型付け |
Perl |
sub を用いた無名サブルーチン |
f(a)->(a)・・・->(a) |
$f->(a)・・・ の場合あり |
Go言語 |
func を用いた無名関数 |
f(a)(a)・・・(a) |
静的型付け |
PHP |
function とuse を用いた無名関数 |
f(a)(a)・・・(a) |
7.4よりfn と=> を用いた無名関数可 |
Standard ML | 空白区切り | f a a ・・・a |
|
Julia |
-> を用いた無名関数 |
f(a)(a)・・・(a) |
|
Emacs Lisp |
lambda による無名関数 |
(funcall・・・(funcall (f a) a)・・・a) |
24.1以降でレキシカルスコープを有効に |
Common Lisp |
lambda による無名関数 |
(funcall・・・(funcall (f a) a)・・・a) |
|
R言語 |
function による無名関数 |
f(a)(a)・・・(a) |
|
C++ |
[] を用いた無名関数 |
f(a)(a)・・・(a) |
C++11以降 |
Java |
-> を用いた無名関数 |
f.apply(a).apply(a)・・・.apply(a) |
Java 8以降,静的型付け |
Clojure |
fn による無名関数 |
(・・・((f a) a)・・・a) |
#Haskell(GHC)
関数定義時に(,)
を用いない空白区切りの引数指定を行えばカリー化される.カリー化関数の引数指定は関数 引数 引数 ・・・ 引数
である.
Prelude> func (x,y,z) = if x > 0 then y else z
Prelude> func((-100),0,(-1))
-1
Prelude> func x y z = if x > 0 then y else z
Prelude> func (-100) 0 (-1)
-1
#Scheme(Gauche)
lambda
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は(・・・((関数 引数) 引数)・・・引数)
である.なお,(GaucheやSCMなどの)処理系によっては,引数指定時の形式に沿った(lambda
を省略した)関数定義でも可能である.
gosh> (define (func x y z) (if (> x 0) y z))
func
gosh> (func -100 0 -1)
-1
gosh> (define func (lambda (x) (lambda (y) (lambda (z) (if (> x 0) y z)))))
func
gosh> (((func -100) 0) -1)
-1
gosh> (define (((func x) y) z) (if (> x 0) y z))
func
gosh> (((func -100) 0) -1)
-1
#Python(Python3,Python2)
lambda
を用いた無名関数を用いることもできるが,無名関数を直接変数に代入するのがPEP8非推奨ということもあり,関数内部で定義した関数を戻り値にする方法が一般的である.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
>>> def func(x, y, z): return y if x > 0 else z
...
>>> func(-100, 0, -1)
-1
>>> def func(x):
... def func(y):
... def func(z): return y if x > 0 else z
... return func
... return func
...
>>> func(-100)(0)(-1)
-1
>>> func = lambda x: lambda y: lambda z: y if x > 0 else z # PEP8非推奨
>>> func(-100)(0)(-1)
-1
#Ruby(CRuby,JRuby)
Rubyでは,カリー化するメソッドcurry
が用意されている.ただし,複数引数の無名関数を->
によって一度定義してからcurry
を適用する.カリー化関数の引数指定は関数.(引数).(引数)・・・.(引数)
または関数[引数][引数]・・・[引数]
である.
def func1(x,y,z) x > 0 ? y : z end
p func1(-100,0,-1) # => -1
func2 = -> x,y,z { x > 0 ? y : z }
p func2.curry.(-100).(0).(-1) # => -1
p func2.curry[-100][0][-1] # => -1
->
のみを用いた無名関数によって実現する方法は次の通り.
func3 = -> x { -> y { -> z { x > 0 ? y : z } } }
p func3.(-100).(0).(-1) # => -1
p func3[-100][0][-1] # => -1
なお,無名関数の定義にも複数の記法があり,いずれの記法も二種類の引数指定が可能である.
Proc.new{ |x| Proc.new{ |y| Proc.new{ |z| x > 0 ? y : z } } }.(-100).(0).(-1)
lambda{ |x| lambda{ |y| lambda{ |z| x > 0 ? y : z } } }.(-100).(0).(-1)
proc{ |x| proc{ |y| proc{ |z| x > 0 ? y : z } } }.(-100).(0).(-1)
-> x { -> y { -> z { x > 0 ? y : z} } }.(-100).(0).(-1)
Proc.new{ |x| Proc.new{ |y| Proc.new{ |z| x > 0 ? y : z } } }[-100][0][-1]
lambda{ |x| lambda{ |y| lambda{ |z| x > 0 ? y : z } } }[-100][0][-1]
proc{ |x| proc{ |y| proc{ |z| x > 0 ? y : z } } }[-100][0][-1]
-> x { -> y { -> z { x > 0 ? y : z} } }[-100][0][-1]
#JavaScript(Node.js)
=>
やfunction
を用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
function func1(x,y,z) { return x > 0 ? y : z }
console.log(func1(-100,0,-1)) // => -1
func2 = x => y => z => x > 0 ? y : z
console.log(func2(-100)(0)(-1)) // => -1
function func3(x) {
return function (y) {
return function (z) {
return x > 0 ? y : z
}
}
}
console.log(func3(-100)(0)(-1)) // => -1
#Scala(Scala 2.11 + Java VM 12)
Scalaでは,カリー化するメソッドcurried
が用意されている.ただし,複数引数の無名関数を=>
によって一度定義してからcurried
を適用する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
scala> def func(x: Int, y: Int, z: Int): Int = if (x > 0) y else z
func: (x: Int, y: Int, z: Int)Int
scala> func(-100,0,-1)
res0: Int = -1
scala> val func = (x: Int, y: Int, z: Int) => if (x > 0) y else z
func: (Int, Int, Int) => Int = <function3>
scala> val func_curried = func.curried
func_curried: Int => (Int => (Int => Int)) = <function1>
scala> func_curried(-100)(0)(-1)
res1: Int = -1
=>
のみを用いた無名関数によって実現する方法は次の通り.静的型付け言語であるため,関数全体の型の推移を明示する必要がある.
scala> val func: Int => (Int => (Int => Int)) = (x: Int) => (y: Int) => (z: Int) => if (x > 0) y else z
func: Int => (Int => (Int => Int)) = <function1>
scala> func(-100)(0)(-1)
res2: Int = -1
#Perl(perl 5)
sub
を用いた無名関数(サブルーチン)を戻り値にして実現する.カリー化関数の引数指定は関数(引数)->(引数)・・・->(引数)
である.なお,関数本体の名前も無名関数とした場合は$関数->(引数)->(引数)・・・->(引数)
である.
sub func { my ($x,$y,$z) = @_; $x > 0 ? $y : $z; };
print func(-100,0,-1), "\n"; # => -1
sub func_curried { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; };
print func_curried(-100)->(0)->(-1), "\n"; # => -1
my $func_curried2 = sub { my $x = shift; return sub { my $y = shift; return sub { my $z = shift; return $x > 0 ? $y : $z; }; }; };
print $func_curried2->(-100)->(0)->(-1), "\n"; # => -1
#Go言語(gc)
func
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.なお,静的型付け言語であるため,扱う引数が増えるほど,各引数の関数の戻り値に対する型付け記述が増えていく.
package main
import "fmt"
func func1 (x, y, z int) int { if x > 0 { return y } else { return z } }
func func2 (x int) func(int) func(int) int {
return func(y int) func(int) int {
return func(z int) int {
if x > 0 { return y } else { return z }
}
}
}
func main() {
fmt.Println(func1(-100,0,-1)) // => -1
fmt.Println(func2(-100)(0)(-1)) // => -1
}
#PHP(PHP 7.3,PHP 7.4)
PHP7.3までは,function
とuse
を用いた無名関数を戻り値にする方法で実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
<?php
function func1($x,$y,$z) {
return ($x > 0) ? $y : $z;
}
echo func1(-100,0,-1) . PHP_EOL;
// => -1
function func2($x) {
return function($y) use ($x) {
return function($z) use ($x,$y) {
return ($x > 0) ? $y : $z;
};
};
}
echo func2(-100)(0)(-1) . PHP_EOL;
// => -1
PHP 7.4からは,fn
と=>
を用いた無名関数が利用可能.
function func2($x) { return fn($y) => fn($z) => ($x > 0) ? $y : $z; }
echo func2(-100)(0)(-1) . PHP_EOL;
// => -1
#Standard ML(SML/NJ, Moscow ML)
関数定義時に(,)
を用いない空白区切りの引数指定を行うか,fn
と=>
を用いた関数式によってカリー化される.カリー化関数の引数指定は関数 引数 引数 ・・・ 引数
である.下記実行例はSML/NJ.
- fun func (x, y, z) = if x > 0 then y else z;
val func = fn : int * 'a * 'a -> 'a
- func (~100, 0, ~1);
val it = ~1 : int
- fun func x y z = if x > 0 then y else z;
val func = fn : int -> 'a -> 'a -> 'a
- val func = fn x => fn y => fn z => if x > 0 then y else z;
val func = fn : int -> 'a -> 'a -> 'a
- func ~100 0 ~1;
val it = ~1 : int
- func (~100)(0)(~1);
val it = ~1 : int
#Julia(Version 1.0.5)
->
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
julia> func1(x,y,z) = x > 0 ? y : z
func1 (generic function with 1 method)
julia> func1(-100,0,-1)
-1
julia> func2 = x -> y -> z -> x > 0 ? y : z
#3 (generic function with 1 method)
julia> func2(-100)(0)(-1)
-1
#Emacs Lisp(GNU Emacs 24.1以降)
lexical-binding
を有効に(setq
でt
を設定)すればレキシカルスコープモードとなり,lambda
式を含めてクロージャが利用可能となる.カリー化の引数指定は(funcall・・・(funcall (関数 引数) 引数)・・・引数)
である.
ELISP> (defun func (x y z) (if (> x 0) y z))
func
ELISP> (func -100 0 -1)
-1 (#o7777777777, #x3fffffff)
ELISP> (defun func (x) (lambda (y) (lambda (z) (if (> x 0) y z))))
func
ELISP> (funcall (funcall (func -100) 0) -1)
*** Eval error *** Symbol’s value as variable is void: x
ELISP> (setq lexical-binding t)
t
ELISP> (defun func (x) (lambda (y) (lambda (z) (if (> x 0) y z))))
func
ELISP> (funcall (funcall (func -100) 0) -1)
-1 (#o7777777777, #x3fffffff)
#Common Lisp(SBCL 2.0.0)
lambda
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は(funcall・・・(funcall (関数 引数) 引数)・・・引数)
である.
* (defun func (x y z) (if (> x 0) y z))
FUNC
* (func -100 0 -1)
-1
* (defun func (x) (lambda (y) (lambda (z) (if (> x 0) y z))))
FUNC
* (funcall (funcall (func -100) 0) -1)
-1
#R言語(4.0.2)
function
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
> func <- function(x,y,z) { if (x > 0) y else z }
> func(-100,0,-1)
[1] -1
> func <- function(x) { function(y) { function(z) { if (x > 0) y else z } } }
> func(-100)(0)(-1)
[1] -1
#C++(C++11以降)
[]
を用いた無名関数を戻り値にして実現する.内包される外部変数は[]
内に『キャプチャ』する必要がある.戻り値としての関数の型はstd::function
だが,auto
が手軽である.カリー化関数の引数指定は関数(引数)(引数)・・・(引数)
である.
#include <iostream>
using namespace std;
int func1(int x, int y, int z) { if (x > 0) return y; else return z; }
auto func2(int x) {
return [x] (int y) {
return [x,y] (int z) {
if (x > 0) return y; else return z;
};
};
}
int main(void)
{
cout << func1(-100,0,-1) << endl; // => -1
cout << func2(-100)(0)(-1) << endl; // => -1
return (0);
}
#Java(Java 8以降)
->
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は関数.apply(引数).apply(引数)・・・.apply(引数)
である.なお,静的型付け言語であるため,Function
インタフェースを利用することを前提に,扱う引数が増えるほど,各引数の関数の戻り値に対する型付け記述が増えていく.
jshell> int func1(int x, int y, int z) { return (x > 0) ? y : z; }
| created method func1(int,int,int)
jshell> System.out.println(func1(-100,0,-1))
-1
jshell> Function<Integer, Function<Integer, Function<Integer, Integer>>>
...> func2 = x -> y -> z -> (x > 0) ? y : z
func2 ==> $Lambda$15/0x7426f828@d25987
jshell> System.out.println(func2.apply(-100).apply(0).apply(-1))
-1
#Clojure
fn
を用いた無名関数を戻り値にして実現する.カリー化関数の引数指定は(・・・((関数 引数) 引数)・・・引数)
である.
user=> (defn func [x y z] (if (> x 0) y z))
#'user/func
user=> (func -100 0 -1)
-1
user=> (def func (fn [x] (fn [y] (fn [z] (if (> x 0) y z)))))
#'user/func
user=> (((func -100) 0) -1)
-1
#備考
##カリー化の概要
カリー化とは,高階関数の機能を利用して,複数の引数を指定する関数を,ひとつの引数のみを指定する関数の繰り返しに変換することである.処理記述の共有が可能な『部分適用』の手段として有名であるが,あくまで利用例のひとつである.カリー化自体の特徴は,ラムダ計算などの数学的な理論を適用しやすいことに加え,引数を再帰的に受け取る汎用的な関数を定義したり,引数ごとに値の適用を調節したり,データ構造を要素ごとに受け取ったりすることで,より簡潔で柔軟なプログラミングが可能となることである.
カリー化のメリットについては,こちらの記事の次の言葉がわかりやすい.
カリー化では、部分適用のように$x$を隠し、$y$を変数として見る場合に加えて、$y$を隠し、$x$を変数として見ることができるようになっている。
Pythonで例を示すと,次のようになるだろうか.
>>> def func(x):
... def func(y): return (x - y)
... return func
...
>>> fx = func(5)
>>> [fx(y) for y in range(10)] # xの値(5)を隠し,yを変数(0〜9)と見る場合
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
>>> fy = [func(x) for x in range(10)] # yの値(5)を隠し,xを変数(0〜9)と見る場合
>>> [f(5) for f in fy]
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
なお,今回の記述方法を用いることで,複数引数をもつ既存の関数のカリー化も容易に行える.ただし,複数引数をもつ既存の関数をカリー化関数に変換してくれる関数やマクロは作成できない.理由は,既存関数の引数の数が不定であり,任意の数の無名関数や内部関数を(クロージャ機能を含めて)生成できないためである.RubyやScalaのカリー化メソッドは,個別定義された複数引数をもつ無名関数から変換している.
##記事に関する補足
- PEP8の
lambda
式代入非推奨はカリー化でもやっぱりつらたん.
##変更履歴
- 2020-12-09:Clojureの例を追加
- 2020-12-01:Standard MLの例に関数式表現とMoscow MLを追記
- 2020-09-17:記事タイトルを『チートシート』から『記述まとめ』に変更
- 2020-08-28:C++,Javaの例を追加
- 2020-08-14:冒頭に『記述例一覧版』記事へのリンクを追記
- 2020-08-14:Rubyの例に追記(無名関数の他の記法)
- 2020-08-12:R言語の例を追加
- 2020-08-12:Emacs Lisp,Common Lispの例を追加
- 2020-08-06:Schemeの例に追記(コメントより)
- 2020-08-06:Juliaの例を追加
- 2020-08-04:Standard MLの例を追加
- 2020-08-04:カリー化の概要のPython記述例を変更
- 2020-08-04:Python利用例を削除(以前のPython利用例はこちら,Scheme版はこちら)
- 2020-08-04:カリー化の概要にPython記述例を追加
- 2020-08-04:記事に関する補足の欄を追加
- 2020-08-03:カリー化の概要説明部分を修正(コメントより)
- 2020-08-03:Rubyのカリー化関数を追加(コメントより)
- 2020-08-03:PHP 7.4を追加(コメントより)
- 2020-08-02:PHPを追加
- 2020-08-02:記法のみの一覧を追加
- 2020-08-02:Go言語を追加
- 2020-08-02:初版公開(Haskell,Scheme,Python,Ruby,JavaScript,Scala,Perl,Python利用例)