2 {-# LANGUAGE AllowAmbiguousTypes #-}
4 {-# LANGUAGE BangPatterns #-}
6 {-# LANGUAGE DataKinds #-}
8 {-# LANGUAGE ExistentialQuantification #-}
10 -- {-# LANGUAGE MagicHash #-} -- For unsafeCoerce#
12 -- | This module provides the 'SharingObserver' semantic
13 -- which interprets combinators to observe @let@ definitions
14 -- at the host language level (Haskell),
15 -- effectively turning infinite values into finite ones,
16 -- which is useful for example to inspect
17 -- and optimize recursive grammars.
19 -- Inspired by Andy Gill's [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653).
20 -- For an example, see [symantic-parser](https://hackage.haskell.org/package/symantic-parser).
21 module Symantic.Semantics.SharingObserver where
23 import Control.Applicative (Applicative (..))
24 import Control.Monad (Monad (..))
25 import Control.Monad.Trans.Class qualified as MT
26 import Control.Monad.Trans.Reader qualified as MT
27 import Control.Monad.Trans.State qualified as MT
28 import Control.Monad.Trans.Writer qualified as MT
30 import Data.Eq (Eq (..))
31 import Data.Function (($), (.))
32 import Data.Functor (Functor, (<$>))
33 import Data.Functor.Compose (Compose (..))
34 import Data.HashMap.Strict (HashMap)
35 import Data.HashMap.Strict qualified as HM
36 import Data.HashSet (HashSet)
37 import Data.HashSet qualified as HS
38 import Data.Hashable (Hashable, hash, hashWithSalt)
40 import Data.Maybe (Maybe (..), isNothing)
41 import Data.Monoid (Monoid (..))
42 import Data.Ord (Ord (..))
43 import GHC.StableName (StableName (..), eqStableName, hashStableName, makeStableName)
44 import Prelude (error, (+))
46 import System.IO.Unsafe (unsafePerformIO)
47 import Text.Show (Show (..))
49 import Symantic.Syntaxes.Derive
51 -- * Class 'Referenceable'
53 -- | This class is not for end-users like usual symantic operators,
54 -- though it will have to be defined on end-users' interpreters.
55 class Referenceable letName sem where
56 -- | @('ref' isRec letName)@ is a reference to @(letName)@.
57 -- It is introduced by 'sharingObserver'.
58 -- @(isRec)@ is 'True' iif. this 'ref'erence is recursive,
59 -- ie. appears within its 'define'.
61 -- TODO: index 'letName' with 'a' to enable dependent-map
62 ref :: Bool -> letName -> sem a
63 ref isRec name = liftDerived (ref isRec name)
65 FromDerived (Referenceable letName) sem =>
70 -- * Class 'Definable'
72 -- | This class is not for end-users like usual symantic operators.
73 -- There should be not need to use it outside this module,
74 -- because used 'define's are gathered in 'Letsable'.
75 class Definable letName sem where
76 -- | @('define' letName sub)@ let-binds @(letName)@ to be equal to @(sub)@.
77 -- This is a temporary node either replaced
78 -- by 'ref' and an entry in 'lets''s 'LetBindings',
79 -- or removed when no 'ref'erence is made to it.
80 define :: letName -> sem a -> sem a
81 define name = liftDerived1 (define name)
83 FromDerived1 (Definable letName) sem =>
88 -- * Class 'MakeLetName'
89 class MakeLetName letName where
90 makeLetName :: SharingName -> IO letName
92 -- * Type 'SharingName'
94 -- | Note that the observable sharing enabled by 'StableName'
95 -- is not perfect as it will not observe all the sharing explicitely done.
97 -- Note also that the observed sharing could be different between ghc and ghci.
98 data SharingName = forall a. SharingName (StableName a)
100 -- | @('makeSharingName' x)@ is like @('makeStableName' x)@ but it also forces
101 -- evaluation of @(x)@ to ensure that the 'StableName' is correct first time,
102 -- which avoids to produce a tree bigger than needed.
104 -- Note that this function uses 'unsafePerformIO' instead of returning in 'IO',
105 -- this is apparently required to avoid infinite loops due to unstable 'StableName'
106 -- in compiled code, and sometimes also in ghci.
108 -- Note that maybe [pseq should be used here](https://gitlab.haskell.org/ghc/ghc/-/issues/2916).
109 makeSharingName :: a -> SharingName
110 makeSharingName !x = SharingName $ unsafePerformIO $ makeStableName x
112 instance Eq SharingName where
113 SharingName x == SharingName y = eqStableName x y
114 instance Hashable SharingName where
115 hash (SharingName n) = hashStableName n
116 hashWithSalt salt (SharingName n) = hashWithSalt salt n
119 instance Show SharingName where
120 showsPrec _ (SharingName n) = showHex (I# (unsafeCoerce# n))
123 -- * Type 'SharingObserver'
124 newtype SharingObserver letName sem a = SharingObserver
125 { unSharingObserver ::
127 (HashSet SharingName)
128 (MT.State (SharingObserverState letName))
129 (SharingFinalizer letName sem a)
132 -- | Interpreter detecting some (Haskell embedded) @let@-like definitions
133 -- used at least once and/or recursively, in order to replace them
134 -- with the 'lets' and 'ref' combinators.
135 -- See [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653)
137 -- Beware not to apply 'sharingObserver' more than once on the same term
138 -- otherwise some 'define' introduced by the first call
139 -- would be removed by the second call.
144 SharingObserver letName sem a ->
145 WithSharing letName sem a
146 sharingObserver (SharingObserver m) =
148 MT.runReaderT m mempty
149 `MT.runState` SharingObserverState
150 { sharingObserverStateRefs = HM.empty
151 , sharingObserverStateRecs = HS.empty
156 | (letName, refCount) <- HM.elems (sharingObserverStateRefs st)
159 in -- trace (show refs) $
161 (`MT.runReaderT` refs) $
162 unSharingFinalizer fs
164 -- ** Type 'WithSharing'
165 type WithSharing letName sem a =
166 (sem a, HM.HashMap letName (SomeLet sem))
169 -- * Type 'WithSharing'
170 data WithSharing letName sem a = WithSharing
171 { lets :: HM.HashMap letName (SomeLet sem)
175 (forall v. sem v -> sem v) ->
176 WithSharing letName sem a ->
177 WithSharing letName sem a
178 mapWithSharing f ws = WithSharing
179 { lets = (\(SomeLet sem) -> SomeLet (f sem)) <$> lets ws
184 -- ** Type 'SharingObserverState'
185 data SharingObserverState letName = SharingObserverState
186 { sharingObserverStateRefs :: HashMap SharingName (letName, Int)
187 , sharingObserverStateRecs :: HashSet SharingName
190 sharingObserverNode ::
194 Referenceable letName sem =>
195 MakeLetName letName =>
196 SharingObserver letName sem a ->
197 SharingObserver letName sem a
198 sharingObserverNode (SharingObserver m) = SharingObserver $ do
199 let nodeName = makeSharingName m
201 ((letName, seenBefore), seen) <-
205 -- Compose is used to return (letName, seenBefore) along seen
206 -- in the same HashMap lookup.
208 return $ case seenBefore of
210 ((letName, seenBefore), Just (letName, 0))
212 letName = unsafePerformIO $ makeLetName nodeName
213 Just (letName, refCount) ->
214 ((letName, seenBefore), Just (letName, refCount + 1))
217 (sharingObserverStateRefs st)
218 parentNames <- MT.ask
219 if nodeName `HS.member` parentNames
221 -- recursive reference to nodeName:
222 -- update seen references
223 -- and mark nodeName as recursive
227 { sharingObserverStateRefs = seen
228 , sharingObserverStateRecs = HS.insert nodeName (sharingObserverStateRecs st)
230 return $ ref True letName
232 -- non-recursive reference to nodeName:
233 -- update seen references
234 -- and recurse if the nodeName hasn't been seen before
235 -- (would be in a preceding sibling branch, not in parentNames).
236 MT.lift $ MT.put st{sharingObserverStateRefs = seen}
237 if isNothing seenBefore
238 then MT.local (HS.insert nodeName) (define letName <$> m)
239 else return $ ref False letName
241 type instance Derived (SharingObserver letName sem) = SharingFinalizer letName sem
243 ( Referenceable letName sem
244 , MakeLetName letName
249 LiftDerived (SharingObserver letName sem)
251 liftDerived = sharingObserverNode . SharingObserver . return
253 ( Referenceable letName sem
254 , MakeLetName letName
259 LiftDerived1 (SharingObserver letName sem)
262 sharingObserverNode $
264 f <$> unSharingObserver a
266 ( Referenceable letName sem
267 , MakeLetName letName
272 LiftDerived2 (SharingObserver letName sem)
275 sharingObserverNode $
278 <$> unSharingObserver a
279 <*> unSharingObserver b
281 ( Referenceable letName sem
282 , MakeLetName letName
287 LiftDerived3 (SharingObserver letName sem)
289 liftDerived3 f a b c =
290 sharingObserverNode $
293 <$> unSharingObserver a
294 <*> unSharingObserver b
295 <*> unSharingObserver c
297 ( Referenceable letName sem
298 , MakeLetName letName
303 LiftDerived4 (SharingObserver letName sem)
305 liftDerived4 f a b c d =
306 sharingObserverNode $
309 <$> unSharingObserver a
310 <*> unSharingObserver b
311 <*> unSharingObserver c
312 <*> unSharingObserver d
313 instance Referenceable letName (SharingObserver letName sem) where
314 ref = error "[BUG]: sharingObserver MUST NOT be applied twice"
315 instance Definable letName (SharingObserver letName sem) where
316 define = error "[BUG]: sharingObserver MUST NOT be applied twice"
317 instance Letsable letName (SharingObserver letName sem) where
318 lets = error "[BUG]: sharingObserver MUST NOT be applied twice"
320 -- * Type 'SharingFinalizer'
322 -- | Remove 'define' when non-recursive or unused
323 -- or replace it by 'ref', moving 'define's to the top.
324 newtype SharingFinalizer letName sem a = SharingFinalizer
325 { unSharingFinalizer ::
328 (MT.Writer (LetBindings letName sem))
332 type instance Derived (SharingFinalizer _letName sem) = sem
334 (Eq letName, Hashable letName) =>
335 LiftDerived (SharingFinalizer letName sem)
337 liftDerived = SharingFinalizer . pure
339 (Eq letName, Hashable letName) =>
340 LiftDerived1 (SharingFinalizer letName sem)
342 liftDerived1 f a = SharingFinalizer $ f <$> unSharingFinalizer a
344 (Eq letName, Hashable letName) =>
345 LiftDerived2 (SharingFinalizer letName sem)
350 <$> unSharingFinalizer a
351 <*> unSharingFinalizer b
353 (Eq letName, Hashable letName) =>
354 LiftDerived3 (SharingFinalizer letName sem)
356 liftDerived3 f a b c =
359 <$> unSharingFinalizer a
360 <*> unSharingFinalizer b
361 <*> unSharingFinalizer c
363 (Eq letName, Hashable letName) =>
364 LiftDerived4 (SharingFinalizer letName sem)
366 liftDerived4 f a b c d =
369 <$> unSharingFinalizer a
370 <*> unSharingFinalizer b
371 <*> unSharingFinalizer c
372 <*> unSharingFinalizer d
374 ( Referenceable letName sem
379 Referenceable letName (SharingFinalizer letName sem)
381 ref isRec = liftDerived . ref isRec
383 ( Referenceable letName sem
388 Definable letName (SharingFinalizer letName sem)
390 define name body = SharingFinalizer $ do
393 MT.runWriter $ MT.runReaderT (unSharingFinalizer body) refs
394 if name `HS.member` refs
396 -- This 'define' is 'ref'erenced: move it into the result,
397 -- to put it in scope even when some 'ref' to it exists outside of 'body'
398 -- (which can happen when a body-expression is shared),
399 -- and replace it by a 'ref'.
400 MT.lift $ MT.tell $ HM.insert name (SomeLet sem) defs
401 return $ ref False name
402 else -- Remove this unreferenced 'define' node.
403 unSharingFinalizer body
405 -- * Class 'Letsable'
406 class Letsable letName sem where
407 -- | @('lets' defs x)@ let-binds @(defs)@ in @(x)@.
408 lets :: LetBindings letName sem -> sem a -> sem a
409 lets defs = liftDerived1 (lets ((\(SomeLet val) -> SomeLet (derive val)) <$> defs))
412 FromDerived1 (Letsable letName) sem =>
413 LetBindings letName sem ->
418 data SomeLet sem = forall a. SomeLet (sem a)
420 -- ** Type 'LetBindings'
421 type LetBindings letName sem = HM.HashMap letName (SomeLet sem)
424 -- | Not used but can be written nonetheless.
426 ( Letsable letName sem
430 ) => Letsable letName (SharingFinalizer letName sem) where
431 lets defs x = SharingFinalizer $ do
432 ds <- traverse (\(SomeLet v) -> do
433 r <- unSharingFinalizer v
440 -- ** Type 'OpenRecs'
442 -- | Mutually recursive terms, in open recursion style.
443 type OpenRecs letName a = LetRecs letName (OpenRec letName a)
445 -- | Mutually recursive term, in open recursion style.
446 -- The term is given a @final@ (aka. @self@) map
447 -- of other terms it can refer to (including itself).
448 type OpenRec letName a = LetRecs letName a -> a
450 -- | Recursive let bindings.
451 type LetRecs letName = HM.HashMap letName
453 -- | Least fixpoint combinator.
455 fix f = final where final = f final
457 -- | Least fixpoint combinator of mutually recursive terms.
458 -- @('mutualFix' opens)@ takes a container of terms
459 -- in the open recursion style @(opens)@,
460 -- and return that container of terms with their knots tied-up.
462 -- Used to express mutual recursion and to transparently introduce memoization.
464 -- Here all mutually dependent functions are restricted to the same polymorphic type @(a)@.
465 -- See http://okmij.org/ftp/Computation/fixed-point-combinators.html#Poly-variadic
466 mutualFix :: forall recs a. Functor recs => recs ({-finals-} recs a -> a) -> recs a
467 mutualFix opens = fix f
469 f :: recs a -> recs a
470 f recs = ($ recs) <$> opens