3

私は、新しい言語の根底にあるいくつかの概念について考えてきました。最初はオモチャのようなものでしたが、今は本当に意味があるのだろうかと思っています。この質問を Stack Overflow に投稿して、以前に行われたかどうか、フィードバック、アイデア、またはその他の情報を入手できるかどうかを確認します。

私がこれについて考えるようになったのは、主に宣言型プログラミングに関する Jonathan Edward のプレゼンテーションを読んだ後です。それから私はそれを私の古い考えのいくつかと私が現代言語で見たものと混ぜ合わせました.

宣言型プログラミングの背後にある主な考え方は、「何を」対「どのように」です。しかし、それは何度も聞いているので、ほとんど「面白い」という言葉のように、実際には何も語らず、もどかしいものです。

ただし、ジョナサン・エドワードのバージョンでは、最初に遅延評価を強調することから始めました。これには、関数型リアクティブ プログラミング (FRP)という興味深い結果があります。これは、アニメーション付きの FRP の例です (私が作成した構文を使用):

x as time * 2 // time is some value representing the current time
y as x + (2 * 500)

new Point(x, y)

したがって、ここでは、入力が変更されると値が自動的に変更されます。私の好きな言語の 1 つであるDでは、「純粋な」関数と「不純な」関数が区別されていました。純粋関数は、外界との接続がなく、他の純粋関数のみを使用する関数です。そうでなければ、それは不純です。ポイントは、純粋な関数が与えられた引数に対して同じ値を返すことを常に信頼できるということでした。

ここでも同様の推移的な原則が適用されると思います。私たちの不純物はtimeです。time、存在x、したがってy、およびしたがって、によって触れられるものはすべてnew Point(x, y)不純です。しかし、通知(2 * 500)は純粋です。ご覧のとおり、これはコンパイラにその制限がどこにあるかを伝えます。数式を変数で単純化するようなものだと思います。

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

何が純粋で何がそうでないかをコンパイラに伝えることで、プログラムを大幅に単純化できます。もう 1 つのポイントは、熱心なデータまたは変更可能なデータです。ジョナサン・エドワードは、入力は変更可能で積極的であるが、出力は機能的で怠惰であると認識しました。基本的に、新しい入力が与えられると、プログラムはアトミック状態の変更を定義し、出力は単に現在の状態の関数になります。これが重要な理由を知りたい場合は、プレゼンテーションを参照してください。入力が不純です。遅延評価は、原子状態の変更を定義するのに役立ちます。プログラムが手続き的にどのように書かれるかを見てみましょう。

void main ()
{
    String input = "";

    writeln("Hello, world!");
    writeln("What's your name? ");

    input = readln();

    writeln("Hello, %s!", input);
    writeln("What's your friends name? ");

    input = readln();

    writeln("Hello to you too, %s!", input);
}

ここでのbindキーワードは、変更があった場合に次のコードが実行されることを示していますbegin。このmutableキーワードは、入力が遅延ではなく積極的であることを示しています。それでは、「原子状態の変化」がそれをどのように表すかを見てみましょう。

program:
    mutable step := 0

    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")
        ++step

    bind readln() as input when step = 1:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        ++step

    bind readln() as input when step = 2:
        writeln("Hello to you too, %s!", input)

ここで、プログラマーにとってより簡単に、より読みやすくすることができるものを見てきました。まず第一に、醜いstep変数と、それを毎回どのようにインクリメントしてテストする必要があるかです。以下は、新しく改善されたバージョンがどのように見えるかの例です。

program:
    bind begin:
        writeln("Hello, world!")
        writeln("What's your name? ")

    bind readln() as input:
        writeln("Hello, %s!", input)
        writeln("What's your friends name? ")
        yield // This just means the program jumps to here instead of at the beginning
        writeln("Hello to you too, %s!", input)
        halt

その方がいいです。ただし、完璧ではありません。でも完璧な答えを知っていたら、私はここにいないでしょう?

ゲームエンジンを使用したより良い例を次に示します。

class VideoManager:
    bind begin: // Basically a static constructor, will only be called once and at the beginning
        // Some video set up stuff

    bind end: // Basically a static destructor
        // Some video shut down stuff

class Input:
    quitEvent     as handle // A handle is an empty value, but can be updated so code that's bound to it changes.
    keyboardEvent as handle(KeyboardEvent) // This handle does return a value though
    mouseEvent    as handle(MouseEvent)

    // Some other code manages actually updating the handles.

class Sprite:
    mutable x := 0
    mutable y := 0

    bind this.videoManager.updateFrame:
        // Draw this sprite

class FieldState:
    input  as new Input
    player as new Sprite

    bind input.quitEvent:
        halt

    bind input.keyboardEvent as e:
        if e.type = LEFT:
            this.player.x -= 2
        else if e.type = RIGHT:
            this.player.x += 2
        else if e.type = UP:
            this.player.y -= 2
        else if e.type = DOWN:
            this.player.y += 2

これには、コールバック、イベント、さらにはループなども必要なく、スレッドが明らかであることが気に入っています。何が起こっているのかが分かりやすくなり、Python のような構文だけではありません。言語開発者が、人々がラベルと goto を for で使用しているものは、条件付き分岐とループだけであることに気付いたときのようなものだと思います。そのため、if-then-else、while、for を言語に組み込むと、ラベルと goto は非推奨になり、コンパイラと人々は何が起こっているかを知ることができました。私たちが使用するもののほとんどは、そのプロセスから来ています。

スレッドに戻ると、これの良いところは、スレッドがはるかに柔軟であることです。私たちがやりたいことではなく、やりたいことを言うことに近づいたので、コンパイラがやりたいことを自由にできる場合。そのため、コンパイラはマルチコアおよび分散プロセッサを利用できますが、適切なスレッド サポートのないプラットフォームを補うことができます。

最後に 1 つ言いたいことがあります。そして、それがテンプレートに関する私の見解です。これは、私がプログラミングを始めたとき (実際には約 2 年前) に発達し始めた概念的な卵のようなもので、その後、割れ始めました。基本的には抽象化の原則でしたが、クラスやオブジェクトにまで及びました。

それは私が機能をどのように認識したかに関係していました。例えば:

int add (int a, int b)
{
    return a + b;
}

わかりましaddた、 が返されましたが、それはintでしたか? int起こるのを待っているような気がしました。数ピースのないパズルのように。可能性は限られており、特定のピースしか適合しませんでしたが、完成したら、他の場所で使用できる完成品ができました。これは、私が言ったように、抽象化の原則です。抽象化 + 欠落部分 -> 具体的な関係と思われる例をいくつか示します。

  • 関数 + 引数 -> 値
  • 抽象クラス + メソッド -> クラス
  • クラス + インスタンス値 -> オブジェクト
  • テンプレート + 引数 -> 関数またはクラス
  • プログラム + 入力 + 状態 -> 出力

それらはすべて密接に関連しています。これを活かすことができそうです。しかし、どのように?繰り返しますが、これが質問である理由です。しかし、遅延評価はここで興味深いものです。なぜなら、まだ欠落している部分を別のものに渡すことができるからです。コンパイラにとっては、ほとんどの場合、名前を不純物にまで逆参照することです。上記の私の例のように:

(x ^ 2) + 3x + 5
(4 ^ 2) + 3x + 5 = 16 + 3x + 5 = 21 + 3x = 3(7 + x)

コンパイラに提供する部分が多ければ多いほど、コンパイラはそれを完成させ、プログラムを本質的なコアに縮小することができます。上記のadd関数は、外部リソースに依存しないため、コンパイル時に自動的に解決されます。コンパイラがどれだけ賢いかによっては、多くのクラスやオブジェクト、そしてプログラムの大部分を解決することさえできます。

それは今のところすべてです。これらのことの例がすでに行われているのを見たことがあるなら、私はそれを見たいと思います. また、アイデア、イノベーション、リソース、またはフィードバックがあれば、それもよろしくお願いします。

4

1 に答える 1

2

Haskellプログラミング言語を見てみたいと思うことは間違いありません。

Haskell は非常に宣言型であり、遅延評価が組み込まれており、機能的なリアクティブ プログラミング ライブラリさえ存在します。しかし、最も注目すべき点は、Haskell が純粋に関数型であるということです。つまり、すべて、実際にはすべてが純粋です。

問題は、Haskell が IO によって発生する必要な不純物をどのように処理するかということです。

答えは、あなたが提示した考えに非常によく合います。Haskell は、モナドbindと呼ばれる数学的構造を使用します。これは、基本的に、そのような計算を順序付けする関数(>>=中置演算子として)と共に何らかの値を生成する計算を表します。

それでは、IO の例を見てみましょう。行を読んで名前を出力します... IO でさえ純粋なので、単純に何かを実行することはできません。代わりに、より大きな IO 計算を構築します。

do
    putStr "Enter your name: "
    name <- getLine
    putStrLn ("Hello " ++ name)

非常に必須に見えますが、内部的には単なる構文です

(putStr "Enter your name: ") >>
(getLine >>= \name ->
 putStrLn ("Hello " ++ name))

これで、任意の種類の計算に対してbind好きな方法で/>>=を定義できます。実際、あなたが話したことはすべてこの方法で実装できます。FRP でさえもです。

Stackoverflow でモナドや Haskell を探してみてください。このトピックには多くの質問がありました。結局のところ、すべてが型チェックされているため、コンパイラによって正確性が強制されます。

于 2010-11-27T11:50:15.707 に答える