-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFix.hs
151 lines (131 loc) · 4.2 KB
/
Fix.hs
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module : Tungsten.Fix
-- Copyright : (c) Alexandre Moine 2019
-- Maintainer : [email protected]
-- Stability : experimental
--
-- This module provides the 'Fix' operator which can be used to define
-- fixed-point structures
-- (see examples in "Tungsten.Structure.List", "Tungsten.Structure.Tree" ord
-- "Tungsten.Structure.Graph").
--
-- Defining a type in term of 'Fix' gives access to 'cata' and 'buildR'
-- and the \"cata/buildR\" rewrite rule (see comment for 'buildR' for how to
-- use it).
--
-- To use efficiently this module, compile with rewrite rules enabled and
-- the @-fspec-constr@ flag.
--
-- A part of this module was inspired by the "Data.Functor.Foldable" module from the
-- <http://hackage.haskell.org/package/recursion-schemes-5.1.3 recursion-schemes package>.
--
-----------------------------------------------------------------------------
module Tungsten.Fix
( -- * The fixed-point operator
Fix (..)
, fix, unfix
-- * Recursion-schemes
, cata, para, ana, apo, hylo
-- * Tools for rewriting
, Cata, buildR
)
where
import Data.Coerce
import Data.Functor.Classes
import Text.Read
import Data.Function (on)
-- | Operator to define fixed-point types.
newtype Fix f = Fix (f (Fix f))
instance Eq1 f => Eq (Fix f) where
(==) = eq1 `on` unfix
instance Ord1 f => Ord (Fix f) where
compare = compare1 `on` unfix
-- | 'show' is a good consumer.
instance (Functor f, Show1 f) => Show (Fix f) where
showsPrec d = cata go
where
go a =
showParen (d >= 11)
$ showString "Fix "
. liftShowsPrec
(\d' -> showParen (d' >= 11))
(foldr (.) id) 11 a
instance Read1 f => Read (Fix f) where
readPrec = parens $ prec 10 $ do
Ident "Fix" <- lexP
Fix <$> step (readS_to_Prec readsPrec1)
-- | Remove one level of fixed-point.
unfix :: Fix f -> f (Fix f)
unfix (Fix f) = f
{-# INLINE unfix #-}
-- | A synonym for 'Fix'.
fix :: f (Fix f) -> Fix f
fix = Fix
{-# INLINE fix #-}
-- | Catamorphism.
-- Functions defined in terms of 'cata' (or \"good consumers\") are subject
-- to fusion with functions exprimed in terms of 'buildR' (or \"good producers\").
cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = c
where
c = f . fmap c . unfix
{-# INLINE [0] cata #-}
-- | Paramorphism.
-- Functions defined in terms of 'para' are /not/ subject to fusion.
para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para t = p
where
p = t . fmap ((,) <*> p) . unfix
-- | Anamorphism.
-- Defined in terms of 'buildR', so subject to fusion with 'cata'.
ana :: Functor f => (a -> f a) -> a -> Fix f
ana f b = buildR $ \fix' -> let c = fix' . fmap c . f in c b
{-# INLINE ana #-}
-- | Apomorphism.
-- Functions defined in terms of 'apo' are /not/ subject to fusion.
apo :: Functor f => (a -> f (Either (Fix f) a)) -> a -> Fix f
apo g = a where a = fix . (fmap (either id a)) . g
-- | Hylomorphism.
--
-- @
-- hylo f g == 'cata' f . 'ana' g
-- @
hylo :: Functor f => (f b -> b) -> (a -> f a) -> a -> b
hylo f g x = cata f (ana g x)
-- | Type of arguments of 'buildR'.
type Cata f = forall a. (f a -> a) -> a
-- | 'buildR' abstracts the build of a structure with respect to the fixed-point
-- combinator, such that we have the following rewrite rule (named \"cata/buildR\"):
--
-- @
-- cata f (buildR g) = g f
-- @
--
-- When firing, this remove the build of an intermediate structure.
-- A function expressed with 'buildR' is called a /good producer/.
--
-- Note 1. Without rewriting, 'buildR' is just:
--
-- @
-- buildR g = g Fix
-- @
--
-- Note 2. The validity of the \"cata/buildR\" rule is guaranteed by [free theorems
-- of Wadler](https://doi.org/10.1145/99370.99404). They are known to fail in
-- presence of 'seq' and 'undefined', be careful.
--
-- Note 3. If @g = cata@ and a rewriting did not happen,
-- then the \"cata/id\" rule will replace the @cata Fix@ obtained with the inlining
-- of 'buildR' by 'id'.
buildR :: Cata f -> Fix f
buildR g = g Fix
{-# INLINE [1] buildR #-}
{-# RULES
"cata/buildR" [~1] forall (f :: t b -> b) (g :: Cata t).
cata f (buildR g) = g f
-- We cannot target `Fix` since it will be optimized away
"cata/id"
cata coerce = id
#-}