読者です 読者をやめる 読者になる 読者になる

Gaucheの無名関数について - lambda, ^, ^c, cut

背景

とあるネット記事のGaucheのコードを読んでいて^ _ ( 何か処理 )というのがあってこれは何だと思って調べたメモです。
^自体は知っていたのですが、^ _という別の記法もあるのかなと不思議に思ったので。

無名関数

公式ドキュメント: 4.3 手続きを作る

Gaucheの無名関数はlambdaで書きます。

; 無名関数の定義
gosh> (lambda (a b) (+ a b))
#<closure (#f a b)>

; 無名関数を即実行
gosh> ((lambda (a b) (+ a b)) 1 2)
3

lambda^という略記もあります。

Special Form: ^ formals body …

^はlambdaの短い別名です。これはGauche独自の拡張です。

gosh> (^(a b) (+ a b) 1 2)
#<closure (#f a b)>

gosh> ((^(a b) (+ a b)) 1 2)
3
gosh>

lambdaもしくは^は引数部分を(a)ではなくaにすると可変長引数を受け取ります。

variable : 手続きは不定個の引数を取ります。 実引数は新しいリストに集められて、そのリストがvaribleに束縛されます。

((lambda a a) 1 2 3) ⇒ (1 2 3)

; 引数をそのまま返すだけの無名関数
gosh> (^ (x) x)
#<closure (#f x)>

gosh> ((^ (x) x) 10)
10

; 引数定義をカッコで指定しないと、(#f . x)という定義になっている
gosh> (^ x x)
#<closure (#f . x)>

; 実行してみると、リストで渡っているのが分かる
gosh> ((^ x x) 10)
(10)
gosh> ((^ x x) 10 20)
(10 20)

; ちなみにもちろんカッコ付きの方だと引数の個数が合わないとエラーになる
gosh> ((^ (x) x) 10 20)
*** ERROR: wrong number of arguments: #f requires 1, but got 2
    While compiling "(standard input)" at line 8: ((^ (x) x) 10 20)
gosh>

冒頭の^ _はこれを意図した書き方だったのかもしれません。
他の言語でもイディオムとして良くあるように、引数を使わないことを意味するため_にしていたのだと思います。実際使っていなかったので。

またこれらとは別に、^cというのもあります。

Macro: ^c body …

(lambda (c) body …)の短縮表記です。 cには#[_a-z]に含まれる任意の一文字が使えます。

(map (^x (* x x)) ‘(1 2 3 4 5)) ⇒ (1 4 9 16 25)

; 以下の2つは同じなはず
gosh> ((^x (+ 1 2)))
3

gosh> ((^_ (+ 1 2)))
3

スペースがあると前述の(#f . x)になるので注意が必要です。最初この違いに気付けず、スペース付きのまま試して期待通りにいかず悩みました。

gosh> (^x (+ 1 2))
#<closure (#f x)>

gosh> (^ x (+ 1 2))
#<closure (#f . x)>

ちなみに部分適用に向いたcutというのもあります。

Macro: cut expr-or-slot expr-or-slot2 …

[SRFI-26] 手続きを簡潔に書ける便利なマクロです。 いわゆる部分適用を実現するために使えます。

; 引数なしの定義
gosh> (cut + 1 2)
#<closure (#f)>

; 定義して即実行
gosh> ((cut + 1 2))
3

; 2引数の定義
gosh> (cut + <> <>)
#<closure (#f #<identifier srfi-26##<identifier srfi-26#x.126a9c0>.1293ee0> #<identifier srfi-26##<identifier srfi-26#x.126a9c0>.1293e00>)>

gosh> ((cut + <> <>) 1 2)
3

本来はmapとかfor-eachとかそういうやつで使うべきな気がします。

; こう書くより
gosh> (map (^a (+ a 1)) '(1 2 3))
(2 3 4)

; こう書いた方が一時変数置かなくて済むのでスマートでしょ的な
gosh> (map (cut + <> 1) '(1 2 3))
(2 3 4)

余談: 調べ方

今回のような記号(^)に関するリファレンスを調べる場合でもaproposinfoで簡単に辿り着けるのがとても良いです。

; ^を含むリファレンスを探す
gosh> (apropos '^)
^                              (gauche)
^-generator                    (gauche)
^_                             (gauche)
^a                             (gauche)
^b                             (gauche)
^c                             (gauche)
^d                             (gauche)
^e                             (gauche)
^f                             (gauche)
^g                             (gauche)
^h                             (gauche)
^i                             (gauche)
^j                             (gauche)
^k                             (gauche)
^l                             (gauche)
^m                             (gauche)
^n                             (gauche)
^o                             (gauche)
^p                             (gauche)
^q                             (gauche)
^r                             (gauche)
^s                             (gauche)
^t                             (gauche)
^u                             (gauche)
^v                             (gauche)
^w                             (gauche)
^x                             (gauche)
^y                             (gauche)
^z                             (gauche)
define-^x                      (gauche)

gosh> (info '^)
; リファレンスが開く

余談

公開前にプレビューで推敲したらlambdaに著作権発生してた。

f:id:kanno_kanno:20170515231923p:plain