File: Where.hs

package info (click to toggle)
haskell-syb 0.7.2.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 360 kB
  • sloc: haskell: 2,264; makefile: 2
file content (125 lines) | stat: -rw-r--r-- 5,121 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
{-# LANGUAGE DeriveDataTypeable #-}

module Where (tests) where

{-

This example illustrates some differences between certain traversal
schemes. To this end, we use a simple system of datatypes, and the
running example shall be to replace "T1a 42" by "T1a 88". It is our
intention to illustrate a few dimensions of designing traversals.

1. We can decide on whether we prefer "rewrite steps" (i.e.,
monomorphic functions on data) that succeed either for all input
patterns or only if the encounter a term pattern to be replaced. In
the first case, the catch-all equation of such a function describes
identity (see "stepid" below). In the second case, the catch-call
equation describes failure using the Maybe type constructor (see
"stepfail" below). As an intermediate assessment, the failure approach
is more general because it allows one to observe if a rewrite step was
meaningful or not. Often the identity approach is more convenient and
sufficient.

2. We can now also decide on whether we want monadic or simple
traversals; recall monadic generic functions GenericM from
Data.Generics.  The monad can serve for success/failure, state,
environment and others.  One can now subdivide monadic traversal
schemes with respect to the question whether they simply support
monadic style of whether they even interact with the relevant
monad. The scheme "everywereM" from the library belongs to the first
category while "somewhere" belongs to the second category as it uses
the operation "mplus" of a monad with addition. So while "everywhereM"
makes very well sense without a monad --- as demonstrated by
"everywhere", the scheme "somewhere" is immediately monadic.

3. We can now also decide on whether we want rewrite steps to succeed
for all possible subterms, at least for one subterm, exactly for one
subterm, and others.  The various traversal schemes make different
assumptions in this respect.

a) everywhere

   By its type, succeeds and requires non-failing rewrite steps.
   However, we do not get any feedback on whether terms were actually
   rewritten. (Say, we might have performed accidentally the identity
   function on all nodes.)

b) everywhereM

   Attempts to reach all nodes where all the sub-traversals are performed
   in monadic bind-sequence. Failure of the traversal for a given subterm
   implies failure of the entire traversal. Hence, the argument of
   "everywhereM" should be designed in a way that it tends to succeed
   except for the purpose of propagating a proper error in the sense of
   violating a pre-/post-condition. For example, "mkM stepfail" should
   not be passed to "everywhereM" as it will fail for all but one term
   pattern; see "recovered" for a way to massage "stepfail" accordingly.

c) somewhere

   Descends into term in a top-down manner, and stops in a given
   branch when the argument succeeds for the subterm at hand. To this
   end, it takes an argument that is perfectly intended to fail for
   certain term patterns. Thanks to the employment of gmapF, the
   traversal scheme recovers from failure when mapping over the immediate
   subterms while insisting success for at least one subterm (say, branch).
   This scheme is appropriate if you want to make sure that a given
   rewrite step was actually used in a traversal. So failure of the
   traversal would mean that the argument failed for all subterms.

Contributed by Ralf Laemmel, ralf@cwi.nl

-}

import Test.Tasty.HUnit

import Data.Generics
import Control.Monad


-- Two mutually recursive datatypes
data T1 = T1a Int | T1b T2  deriving (Typeable, Data)
data T2 = T2 T1             deriving (Typeable, Data)


-- A rewrite step with identity as catch-all case
stepid (T1a 42) = T1a 88
stepid x        = x


-- The same rewrite step but now with failure as catch-all case
stepfail (T1a 42) = Just (T1a 88)
stepfail _        = Nothing


-- We can let recover potentially failing generic functions from failure;
-- this is illustrated for a generic made from stepfail via mkM.
recovered x = mkM stepfail x `mplus` Just x


-- A test term that comprehends a redex
term42 = T1b (T2 (T1a 42))


-- A test term that does not comprehend a redex
term37 = T1b (T2 (T1a 37))


-- A number of traversals
result1 = everywhere (mkT stepid)    term42   -- rewrites term accordingly
result2 = everywhere (mkT stepid)    term37   -- preserves term without notice
result3 = everywhereM (mkM stepfail) term42   -- fails in a harsh manner
result4 = everywhereM (mkM stepfail) term37   -- fails rather early
result5 = everywhereM recovered      term37   -- preserves term without notice
result6 = somewhere (mkMp stepfail)  term42   -- rewrites term accordingly
result7 = somewhere (mkMp stepfail)  term37   -- fails to notice lack of redex

tests = gshow ( result1,
              ( result2,
              ( result3,
              ( result4,
              ( result5,
              ( result6,
              ( result7 ))))))) @=? output

output = "((,) (T1b (T2 (T1a (88)))) ((,) (T1b (T2 (T1a (37)))) ((,) (Nothing) ((,) (Nothing) ((,) (Just (T1b (T2 (T1a (37))))) ((,) (Just (T1b (T2 (T1a (88))))) (Nothing)))))))"