[Haskell-cafe] About the lazy pattern

Petr Pudlak deb at pudlak.name
Thu May 28 07:18:01 EDT 2009


Hi Ryan, thanks for a nice and thorough explanation. I had trouble
understanding the section of the tutorial as well. Maybe it would deserve to
rewrite to something a bit simpler?

Anyhow, I'd like to ask: Is there a reason for which pattern matching for
single-constructor data types isn't lazy by default? For example, why do I have
to write

> weird ~(x,y) = (1,x)
> crazy = weird crazy

when the compiler knows that the type (x,y) has just the single constructor,
and could automatically introduce the additional laziness without any risk?

    Petr

> Hi nemo.  I had a lot of trouble with that section of the tutorial as
> well, and I believe that once you get it, a lot of Haskell becomes a
> lot simpler.
> 
> The way I eventually figured it out is using this idealized execution
> model for Haskell: you just work by rewriting the left side of a
> function to its right side.  The only question is figuring out *which*
> function to rewrite!
> 
> Here's a simpler example:
> 
> > f 0 = 0
> > f x = x+1
> 
> > g (x:xs) = error "urk"
> > g [] = 2
> > const a b = a
> 
> > ex1 = const (f 1) (g [2,3])
> > ex2 = f (const (g []) (g [1,2]))
> 
> Lets say you wanted to know the value of ex1; you just use rewriting
> 
> ex1
> -- rewrite using ex1
> = const (f 1) (g [2,3])
> -- rewrite using const
> = f 1
> -- rewrite using f (second pattern)
> = 1+1
> -- rewrite using +
> = 2
> 
> But lets say we picked a different order to rewrite...
> 
> ex1
> -- rewrite using ex1
> = const (f 1) (g [2,3])
> -- rewrite using g
> = const (f 1) (error "urk")
> -- rewrite using error
> computation stops
> 
> Of course this is bad, and it was obviously wrong to evaluate g first
> (because const is lazy).  So one heuristic we always use is "rewrite
> the leftmost application first" which avoids this problem.  But lets
> try that rule on ex2!
> 
> ex2
> -- rewrite using ex2
> = f (const (g []) (g [1,2]))
> -- rewrite using f
> = ?
> 
> Unfortunately, now we don't know which pattern to match!  So we have
> to pick a different thing to rewrite.  The next rule is that, if the
> thing you want to rewrite has a pattern match, look at the argument
> for the patterns being matched and rewrite them instead, using the
> same "leftmost first" rule:
> 
> f (const (g []) (g [1,2]))
> -- trying to pattern match f's first argument
> -- rewrite using const
> = f (g [])
> -- still pattern matching
> -- rewrite using g
> = f 2
> -- now we can match
> -- rewrite using f (second pattern)
> = 2+1
> -- rewrite using +
> = 3
> 
> So, back to the original question (I'm rewriting the arguments to
> "client" and "server" for clarity)
> 
> > reqs = client init resps
> > resps = server reqs
> > client v (rsp:rsps) = v : client (next rsp) rsps
> > server (rq:rqs) = process rq : server rqs
> 
> Lets say we are trying to figure out the value of "resps", to print
> all the responses to the screen:
> 
> resps
> -- rewrite using resps
> = server reqs
> -- pattern match in server
> -- rewrite reqs
> = server (client init resps)
> -- pattern match in server
> -- pattern match also in client
> -- rewrite using resps
> = server (client init (server reqs))
> -- pattern match in server, client, then server
> -- rewrite using reqs
> = server (client init (server (client init resps)))
> 
> You see that we are in a loop now; we are stuck trying to pattern
> match and we will never make any progress!
> 
> The "lazy pattern" says "trust me, this pattern will match, you can
> call me on it later if it doesn't!"
> 
> > reqs = client init resps
> > resps = server reqs
> > client v (rsp:rsps) = v : client (next rsp) rsps
> > server (rq:rqs) = process rq : server rqs
> 
> resps
> -- rewrite resps
> = server reqs
> -- server wants to pattern match, rewrite reqs
> = server (client init resps)
> -- Now, the lazy pattern match says "this will match, wait until later"
> -- rewrite using client!
> = let (rsp:rsps) = resps
>    in server (init : client (next rq) rqs)
> -- rewrite using server
> = let (rsp:rsps) = resps
>    in process init : server (client (next rq) rqs)
> 
> We now have a list node, so we can print the first element and
> continue (which requires us to know the code for "process" and "next",
> but you get the idea, I hope!)
> 
> Now of course, you can lie to the pattern matcher:
> 
> > next x = x + 1
> > init = 5
> 
> client init []
> -- rewrite using client
> = let (rsp0:rsps0) = []
>    in init : client (next rsp0) rsps0
> -- rewrite using init
> = let (rsp0:rsps0) = []
>    in 5 : client (next rsp0) rsps0
> 
> -- print 5 and continue to evaluate...
>    let (rsp0:rsps0) = []
>    in client (next rsp0) rsps0
> -- rewrite using client
> = let
>     (rsp0:rsps0) = []
>     (rsp1:rsps1) = rsps0
>    in (next rsp0) : client (next rsp1) rsps1
> -- rewrite using next
> = let
>     (rsp0:rsps0) = []
>     (rsp1:rsps1) = rsps0
>    in rsp0+1 : client (next rsp1) rsps1
> -- + wants to "pattern match" on its first argument
> -- rewrite using rsp0
> computation stops, pattern match failure, (rsp0:rsps0) does not match []
> 
> For this reason, many people (myself included) consider it bad style
> to use lazy/irrefutable pattern matches on data types with more than
> one constructure.  But they are very handy on types with a single
> constructor, like pairs:
> 
> > weird ~(x,y) = (1,x)
> > crazy = weird crazy
> 
> crazy
> -- rewrite crazy
> = weird crazy
> -- rewrite weird
> = let (x0,y0) = crazy
>    in (1, x0)
> -- We really want to know what x0 is now.
> -- But it is the result of a pattern match inside a let; so we need to evaluate
> -- the right hand side of the binding to see if the patterns match.
> -- So, rewrite crazy to attempt to pattern match...
> = let (x0, y0) = weird crazy
>    in (1, x0)
> -- Then, rewrite weird
> = let
>     (x1, y1) = crazy
>     (x0, y0) = (1, x1)
>    in (1, x0)
> -- rewrite x0
> = let
>     (x1, y1) = crazy
>     (x0, y0) = (1, x1)
>    in (1, 1)
> -- garbage collect
> = (1,1)
> 
> I hope this helps!
> 
>   -- ryan


More information about the Haskell-Cafe mailing list