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 (..))
26 import Data.Eq (Eq (..))
27 import Data.Function (($), (.))
28 import Data.Functor (Functor, (<$>))
29 import Data.Functor.Compose (Compose (..))
30 import Data.HashMap.Strict (HashMap)
31 import Data.HashSet (HashSet)
32 import Data.Hashable (Hashable, hash, hashWithSalt)
34 import Data.Maybe (Maybe (..), isNothing)
35 import Data.Monoid (Monoid (..))
36 import Data.Ord (Ord (..))
38 -- import GHC.Exts (Int(..))
39 -- import GHC.Prim (unsafeCoerce#)
40 import GHC.StableName (StableName (..), eqStableName, hashStableName, makeStableName)
42 -- import Numeric (showHex)
44 import Control.Monad.Trans.Class qualified as MT
45 import Control.Monad.Trans.Reader qualified as MT
46 import Control.Monad.Trans.State qualified as MT
47 import Control.Monad.Trans.Writer qualified as MT
48 import Data.HashMap.Strict qualified as HM
49 import Data.HashSet qualified as HS
51 import System.IO.Unsafe (unsafePerformIO)
52 import Text.Show (Show (..))
53 import Prelude (error, (+))
55 import Symantic.Syntaxes.Derive
57 -- * Class 'Referenceable'
59 -- | This class is not for end-users like usual symantic operators,
60 -- though it will have to be defined on end-users' interpreters.
61 class Referenceable letName sem where
62 -- | @('ref' isRec letName)@ is a reference to @(letName)@.
63 -- It is introduced by 'observeSharing'.
64 -- @(isRec)@ is 'True' iif. this 'ref'erence is recursive,
65 -- ie. appears within its 'define'.
67 -- TODO: index 'letName' with 'a' to enable dependent-map
68 ref :: Bool -> letName -> sem a
69 ref isRec name = liftDerived (ref isRec name)
71 FromDerived (Referenceable letName) sem =>
76 -- * Class 'Definable'
78 -- | This class is not for end-users like usual symantic operators.
79 -- There should be not need to use it outside this module,
80 -- because used 'define's are gathered in 'Letsable'.
81 class Definable letName sem where
82 -- | @('define' letName sub)@ let-binds @(letName)@ to be equal to @(sub)@.
83 -- This is a temporary node either replaced
84 -- by 'ref' and an entry in 'lets''s 'LetBindings',
85 -- or removed when no 'ref'erence is made to it.
86 define :: letName -> sem a -> sem a
87 define name = liftDerived1 (define name)
89 FromDerived1 (Definable letName) sem =>
94 -- * Class 'MakeLetName'
95 class MakeLetName letName where
96 makeLetName :: SharingName -> IO letName
98 -- * Type 'SharingName'
100 -- | Note that the observable sharing enabled by 'StableName'
101 -- is not perfect as it will not observe all the sharing explicitely done.
103 -- Note also that the observed sharing could be different between ghc and ghci.
104 data SharingName = forall a. SharingName (StableName a)
106 -- | @('makeSharingName' x)@ is like @('makeStableName' x)@ but it also forces
107 -- evaluation of @(x)@ to ensure that the 'StableName' is correct first time,
108 -- which avoids to produce a tree bigger than needed.
110 -- Note that this function uses 'unsafePerformIO' instead of returning in 'IO',
111 -- this is apparently required to avoid infinite loops due to unstable 'StableName'
112 -- in compiled code, and sometimes also in ghci.
114 -- Note that maybe [pseq should be used here](https://gitlab.haskell.org/ghc/ghc/-/issues/2916).
115 makeSharingName :: a -> SharingName
116 makeSharingName !x = SharingName $ unsafePerformIO $ makeStableName x
118 instance Eq SharingName where
119 SharingName x == SharingName y = eqStableName x y
120 instance Hashable SharingName where
121 hash (SharingName n) = hashStableName n
122 hashWithSalt salt (SharingName n) = hashWithSalt salt n
125 instance Show SharingName where
126 showsPrec _ (SharingName n) = showHex (I# (unsafeCoerce# n))
129 -- * Type 'SharingObserver'
130 newtype SharingObserver letName sem a = SharingObserver
131 { unSharingObserver ::
133 (HashSet SharingName)
134 (MT.State (SharingObserverState letName))
135 (SharingFinalizer letName sem a)
138 -- | Interpreter detecting some (Haskell embedded) @let@ definitions used at
139 -- least once and/or recursively, in order to replace them
140 -- with the 'lets' and 'ref' combinators.
141 -- See [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653)
143 -- Beware not to apply 'observeSharing' more than once on the same term
144 -- otherwise some 'define' introduced by the first call
145 -- would be removed by the second call.
150 SharingObserver letName sem a ->
151 WithSharing letName sem a
152 observeSharing (SharingObserver m) =
154 MT.runReaderT m mempty
155 `MT.runState` SharingObserverState
156 { oss_refs = HM.empty
157 , oss_recs = HS.empty
162 | (letName, refCount) <- HM.elems (oss_refs st)
165 in -- trace (show refs) $
167 (`MT.runReaderT` refs) $
170 -- ** Type 'WithSharing'
171 type WithSharing letName sem a =
172 (sem a, HM.HashMap letName (SomeLet sem))
175 -- * Type 'WithSharing'
176 data WithSharing letName sem a = WithSharing
177 { lets :: HM.HashMap letName (SomeLet sem)
181 (forall v. sem v -> sem v) ->
182 WithSharing letName sem a ->
183 WithSharing letName sem a
184 mapWithSharing f ws = WithSharing
185 { lets = (\(SomeLet sem) -> SomeLet (f sem)) <$> lets ws
190 -- ** Type 'SharingObserverState'
191 data SharingObserverState letName = SharingObserverState
192 { oss_refs :: HashMap SharingName (letName, Int)
193 , oss_recs :: HashSet SharingName
196 observeSharingNode ::
200 Referenceable letName sem =>
201 MakeLetName letName =>
202 SharingObserver letName sem a ->
203 SharingObserver letName sem a
204 observeSharingNode (SharingObserver m) = SharingObserver $ do
205 let nodeName = makeSharingName m
207 ((letName, seenBefore), seen) <-
211 -- Compose is used to return (letName, seenBefore) along seen
212 -- in the same HashMap lookup.
214 return $ case seenBefore of
216 ((letName, seenBefore), Just (letName, 0))
218 letName = unsafePerformIO $ makeLetName nodeName
219 Just (letName, refCount) ->
220 ((letName, seenBefore), Just (letName, refCount + 1))
224 parentNames <- MT.ask
225 if nodeName `HS.member` parentNames
227 -- recursive reference to nodeName:
228 -- update seen references
229 -- and mark nodeName as recursive
234 , oss_recs = HS.insert nodeName (oss_recs st)
236 return $ ref True letName
238 -- non-recursive reference to nodeName:
239 -- update seen references
240 -- and recurse if the nodeName hasn't been seen before
241 -- (would be in a preceding sibling branch, not in parentNames).
242 MT.lift $ MT.put st{oss_refs = seen}
243 if isNothing seenBefore
244 then MT.local (HS.insert nodeName) (define letName <$> m)
245 else return $ ref False letName
247 type instance Derived (SharingObserver letName sem) = SharingFinalizer letName sem
249 ( Referenceable letName sem
250 , MakeLetName letName
255 LiftDerived (SharingObserver letName sem)
257 liftDerived = observeSharingNode . SharingObserver . return
259 ( Referenceable letName sem
260 , MakeLetName letName
265 LiftDerived1 (SharingObserver letName sem)
270 f <$> unSharingObserver a
272 ( Referenceable letName sem
273 , MakeLetName letName
278 LiftDerived2 (SharingObserver letName sem)
284 <$> unSharingObserver a
285 <*> unSharingObserver b
287 ( Referenceable letName sem
288 , MakeLetName letName
293 LiftDerived3 (SharingObserver letName sem)
295 liftDerived3 f a b c =
299 <$> unSharingObserver a
300 <*> unSharingObserver b
301 <*> unSharingObserver c
303 ( Referenceable letName sem
304 , MakeLetName letName
309 LiftDerived4 (SharingObserver letName sem)
311 liftDerived4 f a b c d =
315 <$> unSharingObserver a
316 <*> unSharingObserver b
317 <*> unSharingObserver c
318 <*> unSharingObserver d
319 instance Referenceable letName (SharingObserver letName sem) where
320 ref = error "[BUG]: observeSharing MUST NOT be applied twice"
321 instance Definable letName (SharingObserver letName sem) where
322 define = error "[BUG]: observeSharing MUST NOT be applied twice"
323 instance Letsable letName (SharingObserver letName sem) where
324 lets = error "[BUG]: observeSharing MUST NOT be applied twice"
326 -- * Type 'SharingFinalizer'
328 -- | Remove 'define' when non-recursive or unused
329 -- or replace it by 'ref', moving 'define's to the top.
330 newtype SharingFinalizer letName sem a = SharingFinalizer
331 { unFinalizeSharing ::
334 (MT.Writer (LetBindings letName sem))
338 type instance Derived (SharingFinalizer _letName sem) = sem
340 (Eq letName, Hashable letName) =>
341 LiftDerived (SharingFinalizer letName sem)
343 liftDerived = SharingFinalizer . pure
345 (Eq letName, Hashable letName) =>
346 LiftDerived1 (SharingFinalizer letName sem)
348 liftDerived1 f a = SharingFinalizer $ f <$> unFinalizeSharing a
350 (Eq letName, Hashable letName) =>
351 LiftDerived2 (SharingFinalizer letName sem)
356 <$> unFinalizeSharing a
357 <*> unFinalizeSharing b
359 (Eq letName, Hashable letName) =>
360 LiftDerived3 (SharingFinalizer letName sem)
362 liftDerived3 f a b c =
365 <$> unFinalizeSharing a
366 <*> unFinalizeSharing b
367 <*> unFinalizeSharing c
369 (Eq letName, Hashable letName) =>
370 LiftDerived4 (SharingFinalizer letName sem)
372 liftDerived4 f a b c d =
375 <$> unFinalizeSharing a
376 <*> unFinalizeSharing b
377 <*> unFinalizeSharing c
378 <*> unFinalizeSharing d
380 ( Referenceable letName sem
385 Referenceable letName (SharingFinalizer letName sem)
387 ref isRec = liftDerived . ref isRec
389 ( Referenceable letName sem
394 Definable letName (SharingFinalizer letName sem)
396 define name body = SharingFinalizer $ do
399 MT.runWriter $ MT.runReaderT (unFinalizeSharing body) refs
400 if name `HS.member` refs
402 -- This 'define' is 'ref'erenced: move it into the result,
403 -- to put it in scope even when some 'ref' to it exists outside of 'body'
404 -- (which can happen when a body-expression is shared),
405 -- and replace it by a 'ref'.
406 MT.lift $ MT.tell $ HM.insert name (SomeLet sem) defs
407 return $ ref False name
408 else -- Remove this unreferenced 'define' node.
409 unFinalizeSharing body
411 -- * Class 'Letsable'
412 class Letsable letName sem where
413 -- | @('lets' defs x)@ let-binds @(defs)@ in @(x)@.
414 lets :: LetBindings letName sem -> sem a -> sem a
415 lets defs = liftDerived1 (lets ((\(SomeLet val) -> SomeLet (derive val)) <$> defs))
418 FromDerived1 (Letsable letName) sem =>
419 LetBindings letName sem ->
424 data SomeLet sem = forall a. SomeLet (sem a)
426 -- ** Type 'LetBindings'
427 type LetBindings letName sem = HM.HashMap letName (SomeLet sem)
430 -- | Not used but can be written nonetheless.
432 ( Letsable letName sem
436 ) => Letsable letName (SharingFinalizer letName sem) where
437 lets defs x = SharingFinalizer $ do
438 ds <- traverse (\(SomeLet v) -> do
439 r <- unFinalizeSharing v
446 -- ** Type 'OpenRecs'
448 -- | Mutually recursive terms, in open recursion style.
449 type OpenRecs letName a = LetRecs letName (OpenRec letName a)
451 -- | Mutually recursive term, in open recursion style.
452 -- The term is given a @final@ (aka. @self@) map
453 -- of other terms it can refer to (including itself).
454 type OpenRec letName a = LetRecs letName a -> a
456 -- | Recursive let bindings.
457 type LetRecs letName = HM.HashMap letName
459 -- | Least fixpoint combinator.
461 fix f = final where final = f final
463 -- | Least fixpoint combinator of mutually recursive terms.
464 -- @('mutualFix' opens)@ takes a container of terms
465 -- in the open recursion style @(opens)@,
466 -- and return that container of terms with their knots tied-up.
468 -- Used to express mutual recursion and to transparently introduce memoization.
470 -- Here all mutually dependent functions are restricted to the same polymorphic type @(a)@.
471 -- See http://okmij.org/ftp/Computation/fixed-point-combinators.html#Poly-variadic
472 mutualFix :: forall recs a. Functor recs => recs ({-finals-} recs a -> a) -> recs a
473 mutualFix opens = fix f
475 f :: recs a -> recs a
476 f recs = ($ recs) <$> opens