2010年05月13日 04:00 [Edit]

(gc)c - block?それってnested functionで出来るよ!

残念ながらBlocksが使えるのはOS Xのみであるが…

PerlエンジニアのためのObjective-C Blocks入門 - unknownplace.org
OS X 10.6 以降の xcode では Objective-C に Blocks というシンタックスが追加されている。

gccであれば、Nested Functions というものが使える。


例えば、以下はcodepadでも動く。

http://codepad.org/V3uDduYT
#include <stdio.h>

int main(int argc, char** argv) {
    int fact(n){
	return n <= 1 ? 1 : n * fact(n-1);
    };
    printf("%d\n", fact(10));
    return 0;
}

この nested function は、以前404 Blog Not Found:Cで強引にたらいを後回しでも紹介したことがある。

blocksとの違いはといえば、関数を無名にはできないこと。だから

Blocks Programming Topics: Getting Started with Blocks
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    return strncmp(left, right, 1);
});

ということは出来ないのだが、これとて

int compare(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    return strncmp(left, right, 1);
};
qsort(myCharacters, 3, sizeof(char *), compare);

とすることで動くようになる。

残念ながらblocksでもnested functionでも、「本当」closureは書けない。というか変数をencloseすることができない。たとえば、以下はsegfaultで落ちてしまう。

http://codepad.org/JXAWS6ok
#include <stdio.h>

typedef int (*fint)(void);

fint make_incr(int n){
    int f(){ return n++; };
    return f;
}

int main(int argc, char** argv) {
    void f() { printf("Hello Nested Function!\n"); };
    f();
    fint fi = make_incr(0);
    fint gi = make_incr(0);
    printf("%d\n", fi());
    printf("%d\n", fi());
    printf("%d\n", fi());
    printf("%d\n", gi());
    return 0;
}

なぜPerlでは

http://codepad.org/jDG5y64E
#!/usr/bin/perl
sub make_incr{
    my $n = shift;
    sub { $n++ };
}

{
    my $fi = make_incr(0);
    my $gi = make_incr(0);
    printf("%d\n", $fi->());
    printf("%d\n", $fi->());
    printf("%d\n", $fi->());
    printf("%d\n", $gi->());
}

このように動くのに、(nested fucntion拡張された)Cでは動かないのかは読者の宿題としておこう。

Dan the (dys)?functional Man

追記:

以下のようにすると、blocksでも変数をencloseすることが出来るようになる。faulist++

#include <stdio.h>
#include <Block.h>

typedef int (^bint)(void);

bint make_incr(int n){
    __block int _n = n;
    return Block_copy(^{ return _n++;});
}

int main(int argc, char** argv) {
    bint fi = make_incr(0);
    bint gi = make_incr(0);
    printf("%d\n", fi());
    printf("%d\n", fi());
    printf("%d\n", fi());
    printf("%d\n", gi());
    return 0;
}

この記事へのトラックバックURL

この記事へのトラックバック
404 Blog Not Found:(gc)c - block?それってnes...
Objective-CのBlocksとかいうやつ【oops】at 2010年05月13日 05:47
この記事へのコメント
#Include <Block.h>
が突破口でした。うまく行った例を追記することにします。
Dan the (dys)?functional Man
Posted by dankogai at 2010年05月15日 00:53
何度もすいません、また間違えた。
Blocks.hじゃなくてBlock.hですね…。
Posted by faulist at 2010年05月13日 18:57
ObjCだとFoundation/Foundation.hをimportした時点でBlocksが使えるようになってますけど、
Cで使うときはBlocks.hをincludeしないとだめですね。

あと最初に僕が貼ったコード微妙に間違ってました…。
typedef int (^bint)();
のときは
bint f = ^int { ... };
で、
typedef int (^bint)(void);
のときは
bint f = ^int(void) { ... };
ですね。
bint f = ^{ ... };
はどっちでもいけるみたいでした。



Posted by faulist at 2010年05月13日 18:53
faulistさん、
そう思ったのですが

typedef int (^bint)(void);

bint make_incr(int n){
__block int _n = n;
bint f = ^{ return _n++;};
return Block_copy(f);
}

をコンパイルしようとすると、error: invalid conversion initializing integer 'int', expected block pointer

となってしまい駄目ですね。_nをどう扱えばいいのかわからないみたい。
Dan the (dys)?functional Man

Posted by dankogai at 2010年05月13日 15:47
コード貼り付けミスった…行末の < は無視してください…。
Posted by faulist at 2010年05月13日 10:04
元記事でもトラックバックでも書かれてますが、Blocksの方は変数をencloseできますよ。
こんな感じにすると意図する動作をするはずです。

fint make_incr(int n) {<
__block int _n = n;<
fint f = ^int { return _n++; };<
return Block_copy(f);<
}
Posted by faulist at 2010年05月13日 09:58