C言語で非決定的計算

http://www.kmonos.net/wlog/96.html#_2319090427

今ある普通の言語の場合、「別に最左である必要はなくて、最大要素のインデックスならどれでもよかった」という実装をすることができません。

「普通の言語」どころか(書いた私が)頭のおかしいコードですが、

> cat max.c
#include <stdio.h>
#include <assert.h>
#include <setjmp.h>

void amb(int x, int y) {
  assert(0);
}

/* longjmpは返値型がvoidなのでint型に合わせる */
int mylongjmp(jmp_buf cont, int ans) {
  longjmp(cont, ans);
  assert(0);
}

/* arrの最大要素(の一つ)のインデックス+1を非決定的に返す */
void find_max_cps(jmp_buf k, double *arr, int len) {
  jmp_buf cont;
  int ans;

  assert(len >= 1);
  if (len == 1) longjmp(k, 1);

  ans = setjmp(cont);
  if (ans == 0) find_max_cps(cont, arr+1, len-1);

  if (arr[0] > arr[ans]) longjmp(k, 1);
  if (arr[0] < arr[ans]) longjmp(k, ans+1);
  amb(mylongjmp(k, 1), mylongjmp(k, ans+1));
}

int find_max(double *arr, int len) {
  jmp_buf cont;
  int ans;
  ans = setjmp(cont);
  if (ans == 0) find_max_cps(cont, arr, len);
  return ans;
}

int main() {
  double a[] = { 0., 1., 2., 3., 1., 2., 3., 2., 3., 3. };
  printf("%d\n", find_max(a, sizeof a / sizeof a[0]));
  return 0;
}
> gcc -O0 max.c
> ./a.out
10
> gcc -O3 max.c
> ./a.out
4
> gcc -v
/usr/lib/gcc/i386-redhat-linux/3.4.6/specs から spec を読み込み中
コンフィグオプション: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-java-awt=gtk --host=i386-redhat-linux
スレッドモデル: posix
gcc バージョン 3.4.6 20060404 (Red Hat 3.4.6-10)

…もちろん実用のためではなくネタです。すみません。

Cf. http://www.kmonos.net/wlog/96.html#_2319090427

非決定性プリミティブというと amb オペレータ なんですが

Cの関数引数の評価順序やunstableなsortにも同じ類の非決定性がある

蛇足: 最初、arr[0] > arr[ans]やarr[0] < arr[ans]を素でarr[0] > ansやarr[0] < ansと書いてしまい、コンパイルエラーも警告も出ないのに実行結果が間違っていて1分ぐらい悩みました。ML万歳。(SMLでもOCamlでもOK)

追記: setjmp/longjmpを使っているのは、C言語でreturn文を「文」ではなく、ambの引数に書ける「式」にする方法が(私には)わからなかったためです。setjmp/longjmpを使うかわりに、(ambの実引数の中で)変数への破壊的代入をしても、同じ非決定的計算が書けます。C言語では副作用完了点をまたがない複数の副作用は未定義動作…とか言い出すと少し話がややこしくなりますが。

追記2: http://d.hatena.ne.jp/ku-ma-me/20090427/p3 にまさに「破壊的代入による実装」がありました! コメント感謝。そういう風に関数呼び出し(副作用完了点の一つ)を跨げば未定義動作にもならないと思います(多分)。