宣言型プログラミングの夢[終了]


26

宣言型プログラミングの夢が実現しなかったのはなぜですか?邪魔になる具体的な障害は何ですか?簡単な例では、なぜ私は言うことができません

sort(A) is defined by sort(A) in perm(A) && asc(sort(A))

自動的にソートアルゴリズムを取得します。permは順列をasc意味し、昇順を意味します。


4
ところで、あなたの具体的な例は、一種すでに利用可能である:gkoberger.github.io/stacksort
デン

3
プロローグを聞いたことがありますか?「Answer Set Programming」を探してください。多くのシステムがデフォルトのロジックに基づいて構築されています。
シュリンゲル

16
さて、この質問には簡単に答えられます。 そのようなシステムを実装しようとします。何がうまくいかなかったのですか?オッズは、あなたが止めたものが他のすべての人を止めたことは良いことです。
エリックリッパー

4
私は、この質問が得ている以上の信用に値すると信じたいと思っています。一見すると、それは簡単だと思うかもしれませんその背後にあるすべてのロジックをプログラムする必要があり、コンピューターはそれほど賢くありません。 ...しかし、あなたは戻ってこの質問をもう一度見て、もう一度考えます、まあ、はい、それは簡単です、そしてあなたはそのすべてのロジックをプログラムする必要があります-そしてコンピュータは必ずしも最も鋭いツールではありません小屋では、本当です-しかし、その説明には、単に表面にあるものよりも多くの深さがあります。
パンツァークライシス

3
ソートアルゴリズムの説明は宣言的です、はい、しかし、それは地獄が効率的でないことは確かです。n!シーケンスには順列があり、最悪の場合、アルゴリズムはそれらをすべて試してソートされたものを見つける必要があります。階乗時間は、シーケンスを処理するアルゴリズムが実行できる程度に劣っています。
ベンジャミンホジソン

回答:


8

いくつかの非常に良い答えがあります。私は議論に貢献しようとします。

Prologの宣言的論理プログラミングのトピックについては、Richard O'Keefeによる素晴らしい本「The Craft of Prolog」があります。非常に非効率的なプログラムを作成できるプログラミング言語を使用して、効率的なプログラムを作成することです。この本では、いくつかのアルゴリズムの効率的な実装について説明しながら(「プログラミングの方法」の章で)、著者は次のアプローチを取ります。

  • 英語で問題を定義する
  • 可能な限り宣言的な実用的なソリューションを作成します。通常、それはあなたがあなたの質問に持っているものをほとんど正確に意味し、ちょうど正しいProlog
  • そこから、実装を改善してより高速にするための手順を実行します

これらの方法を進めながら、私にとって最も啓発的な(私にとって)観察ができました。

はい、実装の最終バージョンは、作成者が開始した「宣言」仕様よりもはるかに効率的です。まだ宣言的で簡潔で理解しやすいものです。その間に起こったのは、最終的な解決策が、初期の解決策が忘れていた問題の特性をキャプチャすることです。

言い換えれば、ソリューションを実装する際に、問題に関する知識をできるだけ多く使用しました。比較する:

すべての要素が昇順であるようなリストの順列を見つける

に:

2つのソート済みリストをマージすると、ソート済みリストが作成されます。すでにソートされているサブリストがある可能性があるため、長さ1のサブリストの代わりに、これらを開始点として使用します。

さておき、あなたが与えたような定義は非常に一般的であるため魅力的です。しかし、順列が組み合わせの問題であるという事実を意図的に無視しているという感覚から逃れることはできません。これはすでにわかっていることです!これは批判ではなく、単なる観察です。

本当の質問:前進する方法は?1つの方法は、コンピューターに宣言している問題に関する知識をできるだけ多く提供することです。

この問題を本当に解決するために私が知っている最善の試みは、アレクサンダーステパノフが共同執筆した書籍「プログラミングの要素」「数学から汎用プログラミングへ」に示されています。悲しいことに、これらの本のすべてを要約する(または完全に理解する)ことはできません。ただし、入力の関連するすべてのプロパティが事前にわかっているという条件の下で、効率的な(または最適な)ライブラリアルゴリズムとデータ構造を定義するアプローチがあります。最終結果は次のとおりです。

  • 明確に定義された各変換は、既に配置されている制約(既知のプロパティ)の改良です。
  • 既存の制約に基づいて、コンピューターに最適な変換を決定させます。

なぜまだそうなっていないのかについては、コンピューターサイエンスは本当に若い分野であり、私たちは今でもそのほとんどの斬新さを真に評価することに取り組んでいます。

PS

「実装を洗練する」ということの意味を味わうために、たとえば、Prologでリストの最後の要素を取得するという簡単な問題を考えてみましょう。標準的な宣言的解決策は次のとおりです。

last(List, Last) :-
    append(_, [Last], List).

ここで、の宣言的な意味append/3は次のとおりです。

List1AndList2連結であるList1とは、List2

二番目の引数であるためappend/3、私たち(アンダースコア)のみ一つの要素を持つリストを持っているし、最初の引数は無視され、我々は(リストの先頭を破棄し、元のリストの分割を取得するList1のコンテキストでappend/3)と需要こと戻る(List2のコンテキストでappend/3)は、実際には要素が1つだけのリストです。したがって、それは最後の要素です。

SWI-Prologのが提供する実際の実装は、しかし、こう述べています。

last([X|Xs], Last) :-
    last_(Xs, X, Last).

last_([], Last, Last).
last_([X|Xs], _, Last) :-
    last_(Xs, X, Last).

これはまだうまく宣言されています。最初から最後まで読んで、それは言います:

リストの最後の要素は、少なくとも1つの要素のリストに対してのみ意味があります。リストの末尾と先頭のペアの最後の要素は、先頭、末尾が空の場合、または空でない末尾の最後です。

この実装が提供される理由は、Prologの実行モデルを取り巻く実際的な問題を回避するためです。理想的には、どの実装が使用されるかに違いはないはずです。同様に、次のように言うこともできます。

last(List, Last) :-
    reverse(List, [Last|_]).

リストの最後の要素は、反転リストの最初の要素です。

宣言型のPrologの良い点について決定的でない議論を行いたい場合は、Stack OverflowのPrologタグの質問と回答のいくつかをご覧ください


2
繰り返し設計プロセスを通じて、宣言型設計が単純な抽象化からより具体的な実装にどのように進むことができるかを示すために+1。
-itsbruce

1
@ボリスこれは良い答えです。その本は私の本棚に座っています。おそらく私がそれを開いた時でしょう。
davidk01

1
@ davidk01優れた書籍の1つ。Prologとプログラミング全般に非常に慣れていることを前提としていますが、プログラミングへのアプローチは実用的で非常に徹底的です。
XXX

2
@Borisこの例は複雑ではありませんが、宣言型言語の真の強さである反復設計プロセスの生産性と、非常に実用的な価値が非常に重要です。宣言型言語は、反復的な改善を実現するための明確で一貫した再帰的なアプローチを提供します。命令型言語はそうではありません。
-itsbruce

1
+1、「良い、宣言的なプロローグについて決定的でない議論をいっぱいにしてください」…私たちが反対する傾向があるということは非常に真実です!
ダニエルライオンズ

50

論理言語はすでにこれを行っています。ソートは、あなたがやっているのと同じように定義できます。

主な問題はパフォーマンスです。コンピューターは多くのものを計算するのに優れているかもしれませんが、本質的に愚かです。コンピューターが行う可能性のある「賢い」決定はすべて、プログラマーによってプログラミングされました。そして、この決定は通常、最終結果がどのように見えるかではなく、この最終結果を段階的に達成する方法によって説明されます。

ゴーレムの物語を想像してください。あなたが彼に抽象的な命令を与えようとすると、せいぜい、彼はそれを非効率的にし、最悪の場合、彼自身、あなたまたは他の誰かを傷つけます。ただし、必要なものを可能な限り詳細に記述すると、タスクが効果的かつ効率的に完了することが保証されます。

使用する抽象化のレベルを決定するのはプログラマーの仕事です。作成しているアプリケーションの場合、高レベルで抽象的に説明し、パフォーマンスヒットを取得するか、パフォーマンスを低下させ、10倍の時間を費やしますが、1000倍のパフォーマンスのアルゴリズムを取得しますか?


6
Golemגולםという言葉が実際に「原材料」、つまりマシン/エンティティが存在できる最も基本的な状態を意味していることを知るのに役立つかもしれません。
-dotancohen

2
宣言型言語は、抽象レベルの低さを本質的に妨げるものではありません。HaskellとStandard MLの両方は、それぞれ異なる方法で、1つの場所で型/関数に関する単純な宣言文を作成し、別の場所で具体的かつ特定の関数実装の範囲を提供し、さらに別の場所で型を実装に一致させる方法を提供します。一方、OO / Imperative言語でのベストプラクティスは、高/単純から始めて実装の詳細を追加することです。違いは、FPでは高い抽象化が容易であり、必須では低いレベルが容易であることです。
-itsbruce

2
前述のいずれの言語でも、OPが望むものをほとんど提供する特定の一致をハードコーディングするのではなく、タイプのプロパティに基づいて特定の実装の選択を自動的に解決することも可能です。Haskellでは、型クラスがこのための重要なツールになります。標準MLでは、ファンクター。
-itsbruce

22
@BARゴーレム= Golumゴーレムは、ユダヤ人の民話からです!
陶酔

10
この答えから得たのは、ラップトップにwriteと書くことです。
ダンJ

45

加えて、陶酔の優れた点、私たちはすでにすなわち可能性の高い変更したり、コンピュータが実際に効率的なコードを生成できるために何かを要求していない状態を記述する、彼らはうまく機能し、多くの場所で宣言型言語を使用していることを追加したいですそのままで:

  • HTMLは、Webページのコンテンツを宣言します。

  • CSSは、Webページ内のさまざまなタイプの要素がどのように見えるかを宣言します。

  • すべてのリレーショナルデータベースには、データベースの構造を宣言するデータ定義言語があります。

  • SQLは、命令型よりも宣言型にはるかに近いものです。何を表示したいかをデータベースに伝え、データベースのクエリプランナーが実際にそれを実現する方法を見つけ出すからです。

  • ほとんどの構成ファイル(.vimrc、.profile、.bashrc、.gitconfig)は、主に宣言的なドメイン固有の言語を使用していると主張できます。


3
GNU Make、XSLT、Angular.jsは広く使用されているものであり、宣言的なものであることに言及します(角度はおそらく定義を少しプッシュしますが)。
マークKコーワン

そのリストに正規表現を追加しましょう。
シュヴェルン

7
人々は宣言言語が一般的であることを忘れがちです。彼らは通常、完全な言語を扱っているわけではありません。そのリストに正規表現を追加します。
スリーブマン

少し教訓的ですが、それでもなお:すべてのデータベースにDDLがあるわけではありません。膨大な数のスキーマレスNoSQLデータベースを考えてください。すべてのリレーショナルデータベースにあるかもしれませんが、すべてのデータベースがあるわけではありません。
モニカの復活-dirkk

1
@dirkkはそれを考えていませんでした。私の答えを修正しました。
Ixrec

17

抽象化は漏れやすい

宣言システムを実装して、必要なものを宣言すると、コンパイラーまたはインタープリターが実行順序を決定します。理論的な利点は、「方法」について考える必要がなく、この実装を詳細に記述する必要がないことです。ただし、汎用コンピューティングの実際には、どのように実装されるかを念頭に置いて、「方法」について考え、あらゆる種類のトリックを記述する必要があります。非常に、非常に、非常に遅い(たとえば、n!で十分なn!操作)。

あなたの特定の例では、あなたが取得するソートアルゴリズムを-それはあなたが良いかさえ多少使用可能なものを取得することを意味するものではありません。あなたの与えられた定義は、(コンパイラがそうするように)文字通り実装されると、より大きなデータセットには使用できないhttp://en.wikipedia.org/wiki/Bogosortになります-技術的には正しいですが、千の数を並べ替えるには永遠が必要です。

一部の制限されたドメインでは、SQLなどの適切な実装を見つけるのにほぼ常にうまくいくシステムを作成できます。特にうまく機能しない汎用コンピューティングでは、たとえばPrologでシステムを作成できますが、最終的に宣言が命令実行順序に正確に変換され、予想される宣言の多くが失われることを視覚化する必要がありますプログラミングの利点。


あなたの言うことは本質的に真実ですが、パフォーマンスの低下は、インターフェース/コントラクトが例えば実行時間などを保証しない限り、漏れやすい抽象化の兆候ではありません。
ヴァレンテリー

3
ピーターズは、パフォーマンスが悪いことは漏れやすい抽象化の兆候だと言っているわけではありません、@ valenterry。どちらかといえば、彼は反対を言っています:良いパフォーマンスを達成するために、実装の詳細は強制的にリークれます。
-itsbruce

2
実装がパフォーマンスにどのように影響するかを理解するために実装を理解する必要があるからといって、抽象化が漏れやすいと言うのは誤解を招くと思います。抽象化の目的は、パフォーマンスについて考える必要からあなたを保護することではありません。
ドバル

1
@jamesqf宣言型プログラミングでは、何かがソートされていることを述べるだけです。ソート順を何らかの変数/プロパティにバインドすることを宣言できます。そして、そうなるでしょう。新しいデータが追加されるたびに並べ替えを明示的に呼び出したり、並べ替え順序を変更したりする必要はありません。
ハイド

1
@jamesqf実際に自分で試してみないと、ポイントを実際に見ることができません(宣言的なアイデアで遊ぶにはQtのQMLをお勧めします)。命令型プログラミングのみを知っている人を想像してみてください。彼らは、実際に試してみるのではなく、OOPまたは関数型プログラミングのポイントを理解しようとします。
ハイド

11

計算的決定性は、宣言的プログラミングが思ったほど簡単であることが証明されていない最も重要な理由です。

比較的簡単に述べることができる多くの問題は、決定不能であるか、NP完全な複雑さを解決することが証明されています。これは、負のクラスと分類、可算性、再帰を考慮に入れるとよく​​起こります。

よく知られているいくつかのドメインでこれを例証したいと思います。

どのCSSクラスを使用するかの決定には、すべてのCSSルールの知識と考慮が必要です。新しいルールを追加すると、他のすべての決定が無効になる場合があります。NP完全問題のために、ネガティブCSSクラスは意図的に言語に追加されませんが、ネガティブクラスの欠如はCSS設計の決定を複雑にします。

(SQL)クエリオプティマイザーには、結合する順序、使用するインデックス、および一時的な結果に割り当てるメモリを決定するという厄介な問題があります。これは既知のNP完全問題であり、データベース設計とクエリの定式化を複雑にします。別の方法で定式化するには、データベースまたはクエリを設計するときに、設計者はクエリオプティマイザーが実行する可能性のあるアクションとアクションの順序を知る必要があります。経験豊富なエンジニアには、主要なデータベースベンダーが使用するヒューリスティックの知識が必要です。

構成ファイルは宣言型ですが、特定の構成は宣言が困難です。たとえば、機能を適切に構成するには、バージョン管理、展開(および展開履歴)、可能な手動オーバーライド、および他の設定との競合を考慮する必要があります。構成を適切に検証することは、NP完全問題になる可能性があります。

その結果、これらの合併症は初心者に驚きを与え、宣言型プログラミングの「美しさ」を壊し、一部のエンジニアに他のソリューションを探させます。経験の浅いエンジニアのSQLからNoSQLへの移行は、基になるリレーショナルデータベースの複雑さによって引き起こされた可能性があります。


2
「NP完全問題のため、負のCSSクラスは意図的に言語に追加されていません」-詳細を教えてください。
ジョンドヴォルザーク

ちょっとした練習ですが、負のCSSセレクターを使用して、これらを3SAT問題に書き換えることができます(最後の句はDOMである)。
ディベッケ

1
小さな追加。CSS 3および4では、負のセレクターが許可されていますが、:not疑似クラスはネストできません。
ディベッケ

2

我々は持っているの違いデジタルロジックの検証では良いの使用に置かれているプログラミング言語のdeclarativenessでを。

通常、デジタルロジックは、レジスタ間の信号のロジックレベルが定義されるレジスタ転送レベル(RTL)で記述されます。より抽象的で宣言的な方法で定義されたプロパティをますます追加していることを確認します。

より宣言的な言語/言語サブセットの1つは、プロパティ仕様言語のPSLと呼ばれます。たとえば、複数のクロックサイクルにわたるshiftおよびaddのすべての論理演算を指定する必要がある乗算器の RTLモデルをテストする場合。の方法でプロパティを記述できますassert that when enable is high, this output will equal the multiplication of these two inputs after no more than 8 clock cycles。PSL記述は、シミュレーションでRTLと一緒にチェックできます。または、RTL記述を保持するためにPSLを正式に証明することができます。

より宣言的なPSLモデルは、RTL記述と同じ動作を記述することを強制しますが、RTLに対して自動的にチェックして一致するかどうかを確認できる十分に異なる方法で記述します。


1

ほとんどの問題は、データのモデル化方法です。宣言型プログラミングはここでは役に立ちません。命令型言語では、多くの機能を実行するライブラリがすでにたくさんあるので、何を呼び出すかを知るだけで済みます。特定の方法で、この宣言型プログラミングを検討することができます(おそらくこれの最良の例はJava 8のStream APIです)。これにより、抽象化はすでに解決されており、宣言型プログラミングは必要ありません。

また、既に述べたように、ロジックプログラミング言語は既に希望どおりに機能しています。問題はパフォーマンスであると言う人もいるかもしれませんが、今日のハードウェアとこの分野での研究により、物事を改善して実稼働で使用できるようになります。実際、PrologはAIに使用されますが、私は学界によってのみ信じています。

汎用プログラミング言語に適用されることに注意してください。ドメイン固有の言語の場合、宣言型言語の方がはるかに優れています。SQLはおそらく最良の例です。


3
データモデリング?あなたは命令が最悪であるものを選んだ。HaskellやMLなどの宣言型関数言語は、データモデリングに優れています。たとえば、代数的データ型と再帰的データ型は、通常、1行または2行で包括的に定義できます。もちろん、あなたはまだ書くべき関数を持っていますが、それらのコードは型定義から容赦なく従い、それによって制約されています。奇妙な比較を行う。
itsbruce

1
@itsbruce実のところ、ほとんどの実際のデータはADTに簡単にマッピングされません。ほとんどのデータベースがどのように機能するかを考えてください。プロローグとして-アーラン、あなたは正しい、彼らは異なる言語です。1つは機能的で、もう1つは論理的ですが、比較全体を削除する場合に最適であると述べました。
m3th0dman

1
@ m3th0dmanデータベースは、大量のタプル/レコードです。Haskellはレコードが不足していますが、タプルがあり、MLには両方があるので、そこに少し障害があります。また、Haskellの場合、新しい擬似レコードデータ型を宣言するのに必要な定型文の量は、静的に型付けされた平均的なOOP言語でfaux-structを作成するのに必要な量よりもはるかに小さくなります。ほとんどのデータがADTに簡単にマッピングされない方法について詳しく説明してください。
ドーバル

1
@ m3th0dmanああ、それがデータベーススキーマがタスクに適した命令型言語で定義されている理由です。ああ、いや、それは宣言型DDLです。実際、データモデリングの全体的なプロセスは、アプリケーションを実装する言語ではなく、それと連携するアプリケーション、データフロー、および構造に関連しています。言語のオブジェクト指向機能とそのORMがサポートするものに一致するように歪められることもありますが、通常はそれは機能ではなく、悪いことです。宣言型言語は、概念/論理データモデルの表現により適しています。
-itsbruce

1
@itsbruce手続き型のパラダイムは、データの定義において宣言型のパラダイムよりも優れていると言っていませんでした。宣言型のパラダイムは、手続き型のパラダイム(汎用言語の場合)よりも優れている(または劣っている)と言っていました。データの操作に関しては、SQLの宣言部分では実際のアプリケーションには不十分です。そうでなければ、誰も手続き型拡張機能を発明して使用しなかったでしょう。この記事に関しては、ブルックスと矛盾する要約からは意見が一致しません。彼は実際のプロジェクトから彼のアイデアを構築しましたが、それらの人は彼らの理論を証明するために傑出したものを構築しませんでした。
m3th0dman

0

これは次のようになります。{(whatever =>ファイルを読み取り、URLを呼び出す)| ただし、これらは実行するアクションであり、結果としてシステムの状態が変化しますが、それはソースからは明らかではありません。

宣言は、有限状態マシンとその遷移を記述することができます。FSMは、状態を次の状態に変更することだけがアクションであっても、アクションのない宣言の反対に似ています。

この方法を使用する利点は、1つだけでなく複数の遷移に適用される述語によって遷移とアクションを指定できることです。

これは少し奇妙に聞こえますが、2008年にこのメソッドを使用するプログラムジェネレータを作成しました。生成されたC ++はソースの2〜15倍です。現在、20,000行の入力から75,000行を超えるC ++があります。これには、一貫性と完全性という2つのことが関係します。

一貫性:x = 8とx = 9の両方、または異なる次の状態のように、同時に真になる2つの述語が矛盾したアクションを意味することはありません。

完全性:すべての状態遷移のロジックが指定されています。これらは、2 ** N個以上の状態を持つN個のサブステートを持つシステムをチェックするのが難しいかもしれませんが、すべてを検証できる興味深い組み合わせ方法があります。1962年に、この種の条件付きコード生成と組み合わせデバッグを使用して、7070マシン用のシステムソートのフェーズ1を書きました。ソートされた8,000行のうち、最初のリリース日からのバグの数は永遠にゼロでした!

ソートの第2段階である12,000行では、最初の2か月で60を超えるエラーが発生しました。これについては他にもたくさんありますが、うまくいきます。自動車メーカーがこの方法を使用してファームウェアをチェックした場合、現在発生している障害は表示されません。


1
これは本当に元の質問に答えません。一貫性と完全性は、ほとんどのプログラミングがまだ手続き型であり、宣言型ではないという事実にどのように影響しますか?
ジェイエルストン

冒頭の段落は、質問自体に対するものではなく、arnaudのanswer Programmers.stackexchange.com/a/275839/67057のポイントへの回答のようです。それはそこのコメントであるはずです(私の画面では、あなたの答えは彼の下にはもうありません)。私が考えるあなたの答えの残りの部分は、宣言少量のコードが同等不可欠大量のコードを生成することができる方法の例示であるが、それは明らかではありません。特に重要な点に関して、あなたの答えにはいくつかの整頓が必要です。
-itsbruce

-3

すべてを宣言的に表現できるわけではありません。

多くの場合、実行のフローを明示的に制御する必要があります

たとえば、次の擬似コード: if whatever read a file call an URL else call an URL write a file 宣言的にどのように表現しますか?

もちろん、エキゾチックな方法がいくつかあります。同様モナド。しかし、これらは通常、手順部分よりも扱いにくく、複雑で、はるかに直感的ではありません。

要するに、環境/システムとの「対話」宣言的ではないという事実に帰着します。I / Oに関連するすべては本質的に手続き的です。いつ、何が起こるべきか、そしてどのような順序で正確に伝える必要があります。

宣言は、純粋に計算に関連するすべてのものに最適です。巨大な関数のように、Xを入力し、Yを取得します。それは素晴らしいことです。この例はHTML、入力はテキスト、出力はブラウザに表示されるものです。


2
私はこれを買いません。なぜあなたの例宣言的ではないのですか?それはあるif/ else、何のような宣言型の代替になります。その場合には?確かにread/ write/のcall部分ではありません。値の宣言的なリストだからです(ラップされていることを{...; ...}意味しているのなら、ラップされていることを意味しません[..., ...]か?)もちろん、リストはただの無料モノイドです。他の多くの人もそうするでしょう。ここでモナドが関連する理由はわかりません。それらは単なるAPIです。Haskellは、ストリーム->モナドから手動での構成を支援するために行きましたが、ロジック言語はリストを自動的に順番に構成できます。
Warbo

2
モナドだけの場合は-1。1.本当にエキゾチックではありません(リストとセットはMonadであり、誰もが使用します)。2.特定のシーケンスで強制的に実行することとは関係ありません(Haskell do notation 必須に見えますが、そうではありません)。宣言/機能言語は、関係と依存関係を指定します。関数Xが入力Yを必要とする場合、XはXの前に生成されます。依存関係を正しく取得すると、適切なイベントシーケンスがそれ自体を定義します。多くの相互作用はイベント駆動であり、1セットのシーケンスではありません。宣言型言語は、イベントへの反応を難しくしません。
-itsbruce

怠azineはその一部を複雑にしますが、怠inessは宣言型言語の定義の一部ではなく、宣言型言語のほとんどはそれを使用しません。そして、そうするものでは、評価を保証する方法はモナドとは関係ありません。宣言型言語が抽象計算ではなくインタラクション専用に使用される例については、順序を指定せず、正しいことを順番に行うことを確実にします。PuppetDSLを探す必要があります。必要なことだけが起こるというボーナスがあります-命令型の言語では簡単にできません。
-itsbruce

1
@itsbruceの例のリアクティブプログラミングに加えて、宣言型と見なされ、環境との相互作用に関するものです。
マチェイピエチョトカ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.