5

「A Gentle Introduction to Haskell」を読んでいますが、早い段階で次の例を使用しています。これは、GHC では正常に機能し、私の脳では恐ろしく機能します。

initial                 = 0
next resp               = resp
process req             = req+1

reqs                    = client initial resps
resps                   = server reqs

server          (req:reqs)   = process req : server reqs
client initial ~(resp:resps) = initial : client (next resp) resps

そして呼び出しコード:
take 10 reqs

私がそれをどのように見ているかは、 がreqs呼び出されclient、引数 0 とresps. したがって、resps今は呼び出される必要はありません...これは再び呼び出さreqsれますか? それはすべてとても無限に思えます...誰かがそれが実際にどのように機能しているかを詳しく説明できれば、私は最も感謝しています!

4

4 に答える 4

12

通常、小さな Haskell プログラムの動作を手作業で解決することには価値があると思います。評価ルールは非常に単純です。覚えておくべき重要なことは、Haskell はnon-strict (別名lazy ) であるということです。式は必要な場合にのみ評価されます。怠惰は、一見無限に見える定義が有用な結果をもたらす理由です。この場合、take手段を使用すると、無限リストの最初の 10 個の要素のみが必要になりますreqs。これらは「必要な」すべてです。

実際には、「ニーズ」は通常、パターン マッチによって引き起こされます。[]たとえば、リスト式は通常、関数適用前と関数適用前を区別できるポイントまで評価され(x:xs)ます。(~の定義のように、 pattern の前にある ' ' はclient、それを遅延 (または反駁不可能) にすることに注意してください。遅延パターンは、式全体が強制されるまで引数を強制しません。)

それを覚えているtakeのは:

take 0 _      = []
take n (x:xs) = x : take (n-1) xs

の評価はtake 10 reqs次のようになります。

take 10 reqs 
      -- definition of reqs
    = take 10 (client initial resps)
      -- definition of client [Note: the pattern match is lazy]
    = take 10 (initial : (\ resp:resps' -> client (next resp) resps') 
                             resps)
      -- definition of take
    = initial : take 9 ((\ resp:resps' -> client (next resp) resps') 
                            resps)
      -- definition of initial
    = 0 : take 9 ((\ resp:resps' -> client (next resp) resps') 
                      resps)
      -- definition of resps
    = 0 : take 9 ((\ resp:resps' -> client (next resp) resps') 
                      (server reqs))
      -- definition of reqs
    = 0 : take 9 ((\ resp:resps' -> client (next resp) resps') 
                      (server (client initial resps)))
      -- definition of client
    = 0 : take 9 ((\ resp:resps' -> client (next resp) resps') 
                      (server (initial : {- elided... -}))
      -- definition of server
    = 0 : take 9 ((\ resp:resps' -> client (next resp) resps') 
                      (process initial : server {-...-}))
      -- beta reduction 
    = 0 : take 9 (client (next (process initial)) (server {-...-})
      -- definition of client 
    = 0 : take 9 (next (process initial) : {-...-})
      -- definition of take 
    = 0 : next (process initial) : take 8 {-...-}
      -- definition of next 
    = 0 : process initial : take 8 {-...-}
      -- definition of process 
    = 0 : initial+1 : take 8 {-...-}
      -- definition of initial 
    = 0 : 1 : take 8 {-...-}
      -- and so on...
于 2008-12-29T15:12:29.723 に答える
11

このコードを理解するには、次の 2 つのスキルが必要です。

  • naturals = (1 : map '\n->n+1' naturals無限の可能性がある「定義」(自然数のセット: )、または処理されたリクエストのリスト) と、実際のデータをこれらの定義にマッピングするプロセスである「縮小」を区別する
  • このクライアント/サーバー アプリケーションの構造を確認してください: これは、互いに通信しているプロセスのペアにすぎません: 「client-server」は悪い名前です。実際には、「wallace-gromit」または「foo-bar」と呼ばれるべきでした。哲学者などの話ですが、対称的です。2 つの当事者はピアです。

Jonが既に述べたように、リダクションは怠惰な方法で動作します (別名「必要による呼び出し」):take 2 naturals最初に自然値の完全なセットを評価するのではなく、最初のものを取得し、それを の前に追加しますtake 1 (map '\n->n+1' naturals)。これは [1,( に還元されます。 1+1) ] = [1,2].

これで、クライアント サーバー アプリの構造は次のようになります (私の目には):

  • serverprocess関数を使用して、リクエストのリストからレスポンスのリストを作成する方法です。
  • client応答に基づいて要求を作成し、その要求の応答を応答のリストに追加する方法です。

よく見ると、どちらも「y:ys から x:xs を作成する方法」であることがわかります。wallaceしたがって、それらを および と均等に呼び出すことができますgromit

client応答のリストだけで呼び出されるかどうかを理解するのは簡単です。

someresponses = wallace 0 [1,8,9]    -- would reduce to 0,1,8,9
tworesponses  = take 2 someresponses -- [0,1]

応答が文字通り知られていないが、によって生成された場合、次のgromitように言うことができます

gromitsfirstgrunt = 0
otherresponses = wallace gromitsfirstgrunt (gromit otherresponses)
twootherresponses = take 2 otherresponses -- reduces to [0, take 1 (wallace (gromit ( (next 0):...) )]
                                          -- reduces to [0, take 1 (wallace (gromit ( 0:... ) )  ) ]
                                          -- reduces to [0, take 1 (wallace (1: gromit (...)  )  ) ]
                                          -- reduces to [0, take 1 (1 : wallace (gromit (...)  ) ) ]
                                          -- reduces to [0, 1 ]

両方のピアのいずれかがディスカッションを「開始」する必要があるため、初期値が に提供されwallaceます。

のパターンの前の ~ にも注意してくださいgromit。これは、リスト引数の内容を減らす必要がないことを Haskell に伝えます。これについては、 Haskellのウィキブックにすばらしいトピックがあります (「Lazy Pattern Matching」を探してください)。

于 2008-12-29T10:20:07.363 に答える
4

Haskell で遊んでからしばらく経ちましたが、Haskell は遅延評価されていると確信しています。つまり、実際に必要なものだけを計算します。reqs は無限に再帰的ですがtake 10 reqs、返されたリストの最初の 10 個の要素しか必要としないため、実際に計算されるのはそれだけです。

于 2008-12-29T08:57:07.780 に答える
0

わかりにくいようです。正確に読んだら、それは簡単だとわかりました。

次?それはアイデンティティです

サーバ?それは単にマップ'\n-> n+1'であるマッププロセスです

クライアント?0:サーバークライアントの書き方はわかりにくいです。例:0:マップ'\ n-> n + 1'[0:マップ'\ n-> n + 1'[0:...]]

于 2009-01-01T21:19:12.993 に答える