English 中文(简体)
相互递归 - 能有人帮忙解释一下这段代码是如何工作的吗?
原标题:
  • 时间:2008-12-29 08:44:49
  •  标签:

我正在阅读《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

And the calling code:
take 10 reqs

我所看到的是,调用reqs,产生对使用参数0和resps调用client的调用。因此,resps现在是否需要被调用...然后再次调用reqs?这似乎是无限的...如果有人能详细说明它实际上是如何工作的,我会非常感激!

最佳回答

我发现手动计算小的 Haskell 程序通常是值得的。评估规则非常简单。记住的关键事情是 Haskell 是 非严格 (也称为 惰性):表达式只在需要时才被求值。惰性是看似无限的定义能够产生有用结果的原因。在这种情况下,使用 take 意味着我们只需要无限列表 reqs 的前 10 个元素:它们是我们所“需要”的。

在实践中,“需要”通常由样式匹配驱动。例如,在函数应用之前,列表表达式通常会被评估到我们可以区分[](x:xs)的点。(请注意,在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...
问题回答

理解该代码需要两个技能:

  • distinguishing between definition , which may be infinite (like the set of natural numbers: naturals = (1 : map ->n+1 naturals), or the list of processed requests) and reduction , which is the process of mapping actual data to these definitions
  • seeing the structure of this client-server application: it s just a pair of processes talking to eachother: client-server is a bad name, really: it should have been called wallace-gromit or foo-bar , or talking philosophers or whatever, but it s symmetrical: the two parties are peers.

As Jon already stated, reduction works in a lazy way (aka call by need ): take 2 naturals would not first evaluate the complete set of naturals, but just take the first one, and prepend that to take 1 (map ->n+1 naturals), which would reduce to [1,(1+1) ] = [1,2].

现在客户端服务器应用程序的结构是这样的(在我看来):

  • server is a way to create a list of responses out of a list of requests by using the process function
  • client is a way to create a request based on a response, and append the response of that request to the list of responses.

如果你仔细看,你会发现两者都是从y:ys中创建x:xs的方法。因此,我们可以平等地称它们为华莱士格罗米特

如果仅使用响应列表调用client,那么理解起来将会很容易。

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

如果响应不是字面上已知的,而是由产生的,我们可以说

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的一个wikibook上有一个很好的话题(查找“惰性模式匹配”)。

我已经有一段时间没有使用Haskell了,但我相当确定它是惰性评估的,这意味着它只计算它实际需要的内容。所以虽然reqs是无限递归的,但是由于take 10 reqs只需要返回列表的前10个元素,因此只计算了这10个元素。

它看起来像是一种良好的混淆。如果你仔细阅读,你会发现它很简单:

下一个?这是身份。

server? it s simply map process which is map ->n+1

client? It s obscure way how to write 0 : server client e.g. 0 : map ->n+1 [0: map ->n+1 [0:...]]





相关问题
热门标签