Cの関数型プログラミングにはどのようなツールがありますか?


153

最近、C(C ++ではなく)で関数型プログラミングを行う方法について多くのことを考えています。明らかに、Cは手続き型言語であり、実際には関数型プログラミングをネイティブでサポートしていません。

言語に関数型プログラミング構造を追加するコンパイラ/言語拡張機能はありますか?GCCは、言語拡張としてネストされた関数を提供します。ネストされた関数は親スタックフレームから変数にアクセスできますが、これはまだ成熟したクロージャーから遠く離れています。

たとえば、Cで本当に役立つと思うのは、関数ポインターが必要な場所ならどこでも、ラムダ式を渡して、関数ポインターに減衰するクロージャーを作成できることです。C ++ 0xにはラムダ式が含まれます(これは素晴らしいと思います)。しかし、私はストレートCに適用できるツールを探しています。

[編集]明確にするために、私は関数型プログラミングにより適したCの特定の問題を解決しようとしているのではありません。私がやりたいのなら、私はそこにどんなツールがあるのか​​知りたいだけです。


1
Cをそうでないものに変えようとするハックや移植性のない拡張機能を探す代わりに、探している機能を提供する言語を使用してみませんか?
ロバートギャンブル、

関連する質問も参照してください:< stackoverflow.com/questions/24995/… >
Andy Brice

その質問は、異なる言語についてです、実際に意味を成していないよう@AndyBrice(C ++と同じ意味での「生態系」を持っていません。
カイルストランド

回答:


40

FFCALLを使用すると、Cでクロージャを構築できます。呼び出しと同等のcallback = alloc_callback(&function, data)関数ポインタを返します。ただし、ガベージコレクションは手動で処理する必要があります。callback(arg1, ...)function(data, arg1, ...)

関連して、AppleのGCCフォークにブロックが追加されました。これらは関数ポインターではありませんが、キャプチャされた変数のストレージを手動で構築して解放する必要を回避しながら、ラムダを渡すことができます(事実上、いくつかのコピーと参照カウントが行われ、構文糖とランタイムライブラリの背後に隠れています)。


89

GCCのネストされた関数を使用してラムダ式をシミュレートできます。実際、私はそれを行うマクロを持っています。

#define lambda(return_type, function_body) \
  ({ \
    return_type anon_func_name_ function_body \
    anon_func_name_; \
  })

このように使用します:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; });

12
これは本当にクールです。これ__fn__は、ブロック内で定義された関数の任意の名前({... })であり、GCC拡張や事前定義されたマクロではないようです。__fn__(GCC定義に非常によく似ている)の名前の選択は、本当に頭を悩ませ、効果のないGCCドキュメントを検索しました。
FooF

1
悲しいことに、これは実際には機能しません。クロージャで何かをキャプチャーしたい場合は、実行可能スタックが必要です。個人的には全然役に立たないと思います。
デミ2017

遠く離れた場所では役に立ちませんが、楽しいです。それが、他の答えが受け入れられる理由です。
Joe D

65

関数型プログラミングはラムダではなく、すべて純粋な関数です。したがって、以下は機能的なスタイルを広く促進します。

  1. 関数の引数のみを使用し、グローバル状態は使用しないでください。

  2. 副作用、つまりprintfやIOを最小限に抑えます。すべての関数で直接副作用を引き起こす代わりに実行できるIOを説明するデータを返します。

これは普通のcで実現でき、魔法は必要ありません。


5
関数型プログラミングのその極端な見方を不合理な点にとらえたと思います。たとえば、map関数を渡すための機能がない場合、の純粋な仕様はどのように役立ちますか?
ジョナサンレナード2014年

27
このアプローチは、マクロを使用してc内で関数型言語を作成するよりもはるかに実用的だと思います。純粋な関数を適切に使用すると、forループの代わりにマップを使用するよりもシステムが改善される可能性が高くなります。
アンディは2014年

6
きっとあなたは地図が出発点に過ぎないことを知っています。ファーストクラスの関数がないと、プログラムは(そのすべての関数が「純粋」であっても)、ファーストクラスの関数と同等のプログラムよりも(情報理論の意味で)低次になります。私の見解では、FPの主な利点は、ファーストクラスの関数でのみ可能なこの宣言型スタイルです。ファーストクラスの機能の欠如から生じる冗長性がFPが提供しなければならないすべてであることを暗示するような誰かを不快にさせていると思います。歴史的にFPという用語は、純粋さよりもファーストクラスの機能を意味していたことに注意してください。
ジョナサンレナード

PS以前のコメントはモバイルからのものでした。不規則な文法は意図的なものではありませんでした。
ジョナサンレナード

1
Andy Tillsの回答は、物事について非常に実用的な見方を示しています。Cで「純粋」にすることは、特別なことを必要とせず、大きな利益をもたらします。ファーストクラスの機能は、「快適な暮らし」に例えられます。便利ですが、純粋なプログラミングスタイルを採用するのが現実的です。なぜ誰もがこれに関連してテールコールを議論しないのだろう。ファーストクラスの機能を必要とする人は、末尾呼び出しも依頼する必要があります。
BitTickler 2018年

17

Hartel&Mullerの著書、Functional Cは、現在(2012-01-02)にあります。http//eprints.eemcs.utwente.nl/1077/(PDFバージョンへのリンクがあります)。


2
(質問に関連するように)この本では機能的なことは何も試みられていません。
ユージーントルマチェフ2014

4
あなたはまったく正しいようです。確かに、序文は、この本の目的が、学生が関数型プログラミングに慣れ親しんだ後に命令型プログラミングを教えることであると述べています。参考までに、これはSOでの最初の回答であり、腐ったリンクで以前の回答にコメントするために評判を必要としなかったという理由だけで回答として書かれました。なぜ人々がこの答えを+1し続けるのかといつも不思議に思っています...そうしないでください!:-)
FooF

1
おそらく、この本はPost-Functional Programmingと呼ばれるべきでした(まず第一に、「Imperative C」はセクシーに聞こえないため、2つ目は関数型プログラミングパラダイムに精通していることを想定していること、3つ目は命令パラダイムが標準の衰退のように見えるために駄洒落として、正しい機能、そしておそらく4番目に、ラリーウォールのポストモダンプログラミングの概念を参照しています。ただし、この本はラリーウォールの記事/プレゼンテーションの前に書かれましたが)。
FooF 2014

そして、これは重複した答えです
PhilT

8

関数型プログラミングスタイルの前提条件は、ファーストクラスの関数です。次に許容できる場合は、ポータブルCでシミュレートできます。

  • 字句スコープバインディング、別名クロージャの手動管理。
  • 関数変数のライフタイムの手動管理。
  • 関数application / callの代替構文。
/* 
 * with constraints desribed above we could have
 * good approximation of FP style in plain C
 */

int increment_int(int x) {
  return x + 1;
}

WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS(increment, increment_int);

map(increment, list(number(0), number(1)); // --> list(1, 2)


/* composition of first class function is also possible */

function_t* computation = compose(
  increment,
  increment,
  increment
);

*(int*) call(computation, number(1)) == 4;

そのようなコードのランタイムは、以下のように小さくすることができます

struct list_t {
  void* head;
  struct list_t* tail;
};

struct function_t {
   void* (*thunk)(list_t*);
   struct list_t* arguments;
}

void* apply(struct function_t* fn, struct list_t* arguments) {
  return fn->thunk(concat(fn->arguments, arguments));
}

/* expansion of WRAP_PLAIN_FUNCTION_TO_FIRST_CLASS */
void* increment_thunk(struct list_t* arguments) {
  int x_arg = *(int*) arguments->head;
  int value = increment_int(x_arg);
  int* number = malloc(sizeof *number);

  return number ? (*number = value, number) : NULL;
}

struct function_t* increment = &(struct function_t) {
  increment_thunk,
  NULL
};

/* call(increment, number(1)) expands to */
apply(increment, &(struct list_t) { number(1), NULL });

本質的には、関数/引数のペアとマクロの束として表されるクロージャーを持つファーストクラスの関数を模倣します。完全なコードはここにあります


7

頭に浮かぶ主なことは、コードジェネレーターの使用です。関数型プログラミングを提供する別の言語でプログラミングし、それからCコードを生成してもよろしいですか?

それが魅力的なオプションでない場合は、CPPを悪用して、その方法の一部を実現することができます。マクロシステムでは、関数型プログラミングのアイデアをエミュレートできます。gccがこのように実装されていると聞いたことがありますが、確認したことはありません。

Cはもちろん関数ポインターを使用して関数を渡すことができます。主な問題はクロージャーの欠如であり、型システムが邪魔になりがちです。M4などのCPPよりも強力なマクロシステムを探索できます。結局のところ、私が提案しているのは、真のCは大きな努力なしではタスクに対応できないことですが、Cを拡張してタスクに対応させることができます。CPPを使用する場合、またはスペクトルの反対側に移動して他の言語からCコードを生成する場合、その拡張はCに最もよく似ています。


これが最も正直な答えです。関数型でCを記述しようとすることは、言語の設計と構造自体と戦うことです。
josiah

5

クロージャを実装したい場合は、アセンブリ言語とスタックのスワッピング/管理を広くする必要があります。それに対して推奨するのではなく、それがあなたがしなければならないことだと言っているだけです。

Cで無名関数を処理する方法がわからない。ただし、フォンノイマンマシンでは、asmで無名関数を実行できます。



2

フェリックス言語は C ++にコンパイルします。C ++を気にしないのなら、それが足がかりになるかもしれません。


9
⁻¹α)作者が明示的に«C ++ではない»と述べたため、β)コンパイラによって生成されたC ++は«人間が読めるC ++»ではないため。γ)C ++標準は関数型プログラミングをサポートしているため、ある言語から別の言語にコンパイラを使用する必要はありません。
Hi-Angel

マクロなどを使用して関数型言語を思い付くと、Cをそれほど気にしないことを自動的に意味します。この答えは有効であり、C ++もCの上にマクロパーティーとして始まったので大丈夫です。その道を十分に下って行けば、新しい言語になってしまいます。それから、それはバックエンドの議論に降りてくるだけです。
BitTickler 2018年

1

かなり多くのプログラミング言語がCで書かれています。そして、それらのいくつかは、ファーストクラスの市民としての機能をサポートしています。 「クロージャ」の場合、たとえばglib2 http://library.gnome.org/devel/gobject/unstable/chapter-signal.html#closure で、少なくとも関数型プログラミングに近づいています。そのため、これらの実装のいくつかを使用して関数型プログラミングを行うことはオプションかもしれません。

まあまたはあなたはオカムル、ハスケル、モーツァルト/オズなどを学びに行くことができます;-)

よろしく


少なくともFPスタイルのプログラミングを十分にサポートしているライブラリを使用することの何がひどい問題なのか教えていただけませんか。
フリードリヒ

1

Cで関数型プログラミングを行う方法は、Cで関数型言語インタープリターを作成することでした。Fexlという名前を付けました。これは、「Function EXpression Language」の略です。

インタープリターは非常に小さく、私のシステムでは-O3を有効にして68Kまでコンパイルします。それもおもちゃではありません。私は自分のビジネスのために作成したすべての新しいプロダクションコード(投資パートナーシップのWebベースの会計処理)に使用しています。

今、私は(1)システムルーチン(fork、exec、setrlimitなど)を呼び出す組み込み関数を追加するか、または(2)Fexlで記述できる関数(たとえば、検索部分文字列の場合)。

モジュールのメカニズムは、「コンテキスト」の概念に基づいています。コンテキストは、シンボルをその定義にマップする関数(Fexlで記述)です。Fexlファイルを読み取る場合、任意のコンテキストで解決できます。これにより、カスタム環境を作成したり、制限付きの「サンドボックス」でコードを実行したりできます。

http://fexl.com


-3

Cについて、機能、構文、またはセマンティクスにしたいことは何ですか?関数型プログラミングのセマンティクスは確かにCコンパイラに追加できますが、完了するまでに、Scheme、Haskellなどの既存の関数型言語のいずれかに相当するものになります。

これらのセマンティクスを直接サポートする言語の構文を学ぶだけの方が、時間を有効に活用できます。


-3

Cについては知らない 私は個人的にスキームから始めました、あなたがそうするのを助けることができるリトル・スキーマーのようないくつかの優れた本があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.