1 {-# LANGUAGE AllowAmbiguousTypes #-} -- For ShowLetName
2 {-# LANGUAGE BangPatterns #-} -- For makeSharingName
3 {-# LANGUAGE DataKinds #-} -- For ShowLetName
4 {-# LANGUAGE ExistentialQuantification #-} -- For SharingName
5 -- {-# LANGUAGE MagicHash #-} -- For unsafeCoerce#
6 module Symantic.ObserveSharing where
8 import Control.Applicative (Applicative(..))
9 import Control.Monad (Monad(..))
10 import Data.Bool (Bool(..))
11 import Data.Eq (Eq(..))
12 import Data.Foldable (foldMap)
13 import Data.Function (($), (.))
14 import Data.Functor ((<$>))
15 import Data.Functor.Compose (Compose(..))
16 import Data.HashMap.Strict (HashMap)
17 import Data.HashSet (HashSet)
18 import Data.Hashable (Hashable, hashWithSalt, hash)
20 import Data.Maybe (Maybe(..), isNothing)
21 import Data.Monoid (Monoid(..))
22 import Data.Ord (Ord(..))
23 import Data.String (String)
24 -- import GHC.Exts (Int(..))
25 -- import GHC.Prim (unsafeCoerce#)
26 import GHC.StableName (StableName(..), makeStableName, hashStableName, eqStableName)
27 -- import Numeric (showHex)
28 import Prelude ((+), error)
30 import System.IO.Unsafe (unsafePerformIO)
31 import Text.Show (Show(..))
32 import qualified Control.Monad.Trans.Class as MT
33 import qualified Control.Monad.Trans.Reader as MT
34 import qualified Control.Monad.Trans.State as MT
35 import qualified Control.Monad.Trans.Writer as MT
36 import qualified Data.HashMap.Strict as HM
37 import qualified Data.HashSet as HS
39 import Symantic.Derive
42 -- | This class is not for end-users like usual symantic operators,
43 -- here 'shareable' and 'ref' are introduced by 'observeSharing'.
44 class Letable letName repr where
45 -- | @('ref' isRec letName)@ is a reference to @(letName)@.
46 -- @(isRec)@ is 'True' iif. this 'ref'erence is recursive,
47 -- ie. is reachable within its 'shareable' definition.
48 ref :: Bool -> letName -> repr a
49 ref isRec n = liftDerived (ref isRec n)
51 FromDerived (Letable letName) repr =>
52 Bool -> letName -> repr a
54 -- | @('shareable' letName x)@ let-binds @(letName)@ to be equal to @(x)@.
55 shareable :: letName -> repr a -> repr a
56 shareable n = liftDerived1 (shareable n)
58 FromDerived1 (Letable letName) repr =>
59 letName -> repr a -> repr a
61 -- * Class 'MakeLetName'
62 class MakeLetName letName where
63 makeLetName :: SharingName -> IO letName
65 -- ** Type 'ShowLetName'
66 -- | Useful on golden unit tests because 'StableName's
67 -- change often when changing unrelated source code
68 -- or even when changing basic GHC or executable flags.
69 class ShowLetName (showName::Bool) letName where
70 showLetName :: letName -> String
72 instance Show letName => ShowLetName 'True letName where
74 -- | Always return @"<hidden>"@,
75 instance ShowLetName 'False letName where
76 showLetName _p = "<hidden>"
78 -- * Type 'SharingName'
79 -- | Note that the observable sharing enabled by 'StableName'
80 -- is not perfect as it will not observe all the sharing explicitely done.
82 -- Note also that the observed sharing could be different between ghc and ghci.
83 data SharingName = forall a. SharingName (StableName a)
84 -- | @('makeSharingName' x)@ is like @('makeStableName' x)@ but it also forces
85 -- evaluation of @(x)@ to ensure that the 'StableName' is correct first time,
86 -- which avoids to produce a tree bigger than needed.
88 -- Note that this function uses 'unsafePerformIO' instead of returning in 'IO',
89 -- this is apparently required to avoid infinite loops due to unstable 'StableName'
90 -- in compiled code, and sometimes also in ghci.
92 -- Note that maybe [pseq should be used here](https://gitlab.haskell.org/ghc/ghc/-/issues/2916).
93 makeSharingName :: a -> SharingName
94 makeSharingName !x = SharingName $ unsafePerformIO $ makeStableName x
96 instance Eq SharingName where
97 SharingName x == SharingName y = eqStableName x y
98 instance Hashable SharingName where
99 hash (SharingName n) = hashStableName n
100 hashWithSalt salt (SharingName n) = hashWithSalt salt n
102 instance Show SharingName where
103 showsPrec _ (SharingName n) = showHex (I# (unsafeCoerce# n))
106 -- * Type 'ObserveSharing'
107 newtype ObserveSharing letName repr a = ObserveSharing { unObserveSharing ::
108 MT.ReaderT (HashSet SharingName)
109 (MT.State (ObserveSharingState letName))
110 (FinalizeSharing letName repr a) }
112 -- | Interpreter detecting some (Haskell embedded) @let@ definitions used at
113 -- least once and/or recursively, in order to replace them
114 -- with the 'shareable' and 'ref' combinators.
115 -- See [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653)
117 -- Beware not to apply 'observeSharing' more than once on the same term
118 -- otherwise some 'shareable' introduced by the first call
119 -- would be removed by the second call.
124 ObserveSharing letName repr a ->
125 WithSharing letName repr a
126 observeSharing (ObserveSharing m) =
127 let (fs, st) = MT.runReaderT m mempty `MT.runState`
129 { oss_refs = HM.empty
130 , oss_recs = HS.empty
132 let refs = HS.fromList $
133 (`foldMap` oss_refs st) $ (\(letName, refCount) ->
134 [letName | refCount > 0]) in
135 --trace (show refs) $
137 (`MT.runReaderT` refs) $
141 data SomeLet repr = forall a. SomeLet (repr a)
143 -- ** Type 'WithSharing'
144 type WithSharing letName repr a =
145 (repr a, HM.HashMap letName (SomeLet repr))
147 -- * Type 'WithSharing'
148 data WithSharing letName repr a = WithSharing
149 { lets :: HM.HashMap letName (SomeLet repr)
153 (forall v. repr v -> repr v) ->
154 WithSharing letName repr a ->
155 WithSharing letName repr a
156 mapWithSharing f ws = WithSharing
157 { lets = (\(SomeLet repr) -> SomeLet (f repr)) <$> lets ws
162 -- ** Type 'ObserveSharingState'
163 data ObserveSharingState letName = ObserveSharingState
164 { oss_refs :: HashMap SharingName (letName, Int)
165 , oss_recs :: HashSet SharingName
166 -- ^ TODO: unused so far, will it be useful somewhere at a later stage?
169 observeSharingNode ::
173 Letable letName repr =>
174 MakeLetName letName =>
175 ObserveSharing letName repr a ->
176 ObserveSharing letName repr a
177 observeSharingNode (ObserveSharing m) = ObserveSharing $ do
178 let nodeName = makeSharingName m
180 ((letName, before), preds) <- getCompose $ HM.alterF (\before ->
181 Compose $ case before of
183 let letName = unsafePerformIO $ makeLetName nodeName
184 return ((letName, before), Just (letName, 0))
185 Just (letName, refCount) -> do
186 return ((letName, before), Just (letName, refCount + 1))
187 ) nodeName (oss_refs st)
188 parentNames <- MT.ask
189 if nodeName `HS.member` parentNames
193 , oss_recs = HS.insert nodeName (oss_recs st)
195 return $ ref True letName
197 MT.lift $ MT.put st{ oss_refs = preds }
199 then MT.local (HS.insert nodeName) (shareable letName <$> m)
200 else return $ ref False letName
202 type instance Derived (ObserveSharing letName repr) = FinalizeSharing letName repr
204 ( Letable letName repr
205 , MakeLetName letName
209 ) => LiftDerived (ObserveSharing letName repr) where
210 liftDerived = observeSharingNode . ObserveSharing . return
212 ( Letable letName repr
213 , MakeLetName letName
217 ) => LiftDerived1 (ObserveSharing letName repr) where
218 liftDerived1 f x = observeSharingNode $ ObserveSharing $
219 f <$> unObserveSharing x
221 ( Letable letName repr
222 , MakeLetName letName
226 ) => LiftDerived2 (ObserveSharing letName repr) where
227 liftDerived2 f x y = observeSharingNode $ ObserveSharing $
228 f <$> unObserveSharing x
229 <*> unObserveSharing y
231 ( Letable letName repr
232 , MakeLetName letName
236 ) => LiftDerived3 (ObserveSharing letName repr) where
237 liftDerived3 f x y z = observeSharingNode $ ObserveSharing $
238 f <$> unObserveSharing x
239 <*> unObserveSharing y
240 <*> unObserveSharing z
241 instance Letable letName (ObserveSharing letName repr) where
242 shareable = error "[BUG]: observeSharing MUST NOT be applied twice"
243 ref = error "[BUG]: observeSharing MUST NOT be applied twice"
244 instance Letsable letName (ObserveSharing letName repr) where
245 lets = error "[BUG]: observeSharing MUST NOT be applied twice"
247 -- * Type 'FinalizeSharing'
248 -- | Remove 'shareable' when non-recursive or unused
249 -- or replace it by 'ref', moving 'shareable's to the top.
250 newtype FinalizeSharing letName repr a = FinalizeSharing { unFinalizeSharing ::
251 MT.ReaderT (HS.HashSet letName)
252 (MT.Writer (LetBindings letName repr))
255 -- ** Type 'LetBindings'
256 type LetBindings letName repr = HM.HashMap letName (SomeLet repr)
258 type instance Derived (FinalizeSharing _letName repr) = repr
262 ) => LiftDerived (FinalizeSharing letName repr) where
263 liftDerived = FinalizeSharing . pure
267 ) => LiftDerived1 (FinalizeSharing letName repr) where
268 liftDerived1 f x = FinalizeSharing $ f <$> unFinalizeSharing x
272 ) => LiftDerived2 (FinalizeSharing letName repr) where
273 liftDerived2 f x y = FinalizeSharing $
274 f <$> unFinalizeSharing x
275 <*> unFinalizeSharing y
279 ) => LiftDerived3 (FinalizeSharing letName repr) where
280 liftDerived3 f x y z = FinalizeSharing $
281 f <$> unFinalizeSharing x
282 <*> unFinalizeSharing y
283 <*> unFinalizeSharing z
285 ( Letable letName repr
289 ) => Letable letName (FinalizeSharing letName repr) where
290 shareable name x = FinalizeSharing $ do
292 if name `HS.member` refs
293 -- This 'shareable' is 'ref'erenced, move it into the result,
294 -- to put it in scope even when some 'ref' to it exists outside of 'x'
295 -- (which can happen when a sub-expression is shared),
296 -- and replace it by a 'ref'.
298 let (repr, defs) = MT.runWriter $ MT.runReaderT (unFinalizeSharing x) refs
299 MT.lift $ MT.tell $ HM.insert name (SomeLet repr) defs
300 return $ ref False name
301 -- Remove 'shareable'.
305 -- * Class 'Letsable'
306 class Letsable letName repr where
307 -- | @('lets' defs x)@ let-binds @(defs)@ in @(x)@.
308 lets :: LetBindings letName repr -> repr a -> repr a
309 lets defs = liftDerived1 (lets ((\(SomeLet val) -> SomeLet (derive val)) <$> defs))
312 FromDerived1 (Letsable letName) repr =>
313 LetBindings letName repr -> repr a -> repr a
315 -- | Not used but can be written nonetheless.
317 ( Letsable letName repr
321 ) => Letsable letName (FinalizeSharing letName repr) where
322 lets defs x = FinalizeSharing $ do
323 ds <- traverse (\(SomeLet v) -> do
324 r <- unFinalizeSharing v