1 {-# LANGUAGE AllowAmbiguousTypes #-} -- For ShowLetName
2 {-# LANGUAGE BangPatterns #-} -- For makeSharingName
3 {-# LANGUAGE DataKinds #-} -- For ShowLetName
4 {-# LANGUAGE DefaultSignatures #-}
5 {-# LANGUAGE ExistentialQuantification #-} -- For SharingName
6 -- {-# LANGUAGE MagicHash #-} -- For unsafeCoerce#
7 module Symantic.Typed.ObserveSharing where
9 import Control.Applicative (Applicative(..))
10 import Control.Monad (Monad(..))
11 import Data.Bool (Bool(..))
12 import Data.Eq (Eq(..))
13 import Data.Foldable (foldMap)
14 import Data.Function (($), (.))
15 import Data.Functor ((<$>))
16 import Data.Functor.Compose (Compose(..))
17 import Data.HashMap.Strict (HashMap)
18 import Data.HashSet (HashSet)
19 import Data.Hashable (Hashable, hashWithSalt, hash)
21 import Data.Maybe (Maybe(..), isNothing)
22 import Data.Monoid (Monoid(..))
23 import Data.Ord (Ord(..))
24 import Data.String (String)
25 -- import GHC.Exts (Int(..))
26 -- import GHC.Prim (unsafeCoerce#)
27 import GHC.StableName (StableName(..), makeStableName, hashStableName, eqStableName)
28 -- import Numeric (showHex)
29 import Prelude ((+), error)
31 import System.IO.Unsafe (unsafePerformIO)
32 import Text.Show (Show(..))
33 import qualified Control.Monad.Trans.Class as MT
34 import qualified Control.Monad.Trans.Reader as MT
35 import qualified Control.Monad.Trans.State as MT
36 import qualified Control.Monad.Trans.Writer as MT
37 import qualified Data.HashMap.Strict as HM
38 import qualified Data.HashSet as HS
40 import Symantic.Typed.Derive
42 --import Debug.Trace (trace)
45 -- | This class is not for end-users like usual symantic operators,
46 -- here 'shareable' and 'ref' are introduced by 'observeSharing'.
47 class Letable letName repr where
48 -- | @('ref' isRec letName)@ is a reference to @(letName)@.
49 -- @(isRec)@ is 'True' iif. this 'ref'erence is recursive,
50 -- ie. is reachable within its 'def'inition.
51 ref :: Bool -> letName -> repr a
52 ref isRec n = liftDerived (ref isRec n)
54 FromDerived (Letable letName) repr =>
55 Bool -> letName -> repr a
57 -- | @('shareable' letName x)@ let-binds @(letName)@ to be equal to @(x)@.
58 shareable :: letName -> repr a -> repr a
59 shareable n = liftDerived1 (shareable n)
61 FromDerived1 (Letable letName) repr =>
62 letName -> repr a -> repr a
64 -- * Class 'MakeLetName'
65 class MakeLetName letName where
66 makeLetName :: SharingName -> IO letName
68 -- ** Type 'ShowLetName'
69 -- | Useful on golden unit tests because 'StableName'
70 -- change often when changing unrelated source code
71 -- or even changing basic GHC or executable flags.
72 class ShowLetName (showName::Bool) letName where
73 showLetName :: letName -> String
75 instance Show letName => ShowLetName 'True letName where
77 -- | Always return @"<hidden>"@,
78 instance ShowLetName 'False letName where
79 showLetName _p = "<hidden>"
81 -- * Type 'SharingName'
82 -- | Note that the observable sharing enabled by 'StableName'
83 -- is not perfect as it will not observe all the sharing explicitely done.
85 -- Note also that the observed sharing could be different between ghc and ghci.
86 data SharingName = forall a. SharingName (StableName a)
87 -- | @('makeSharingName' x)@ is like @('makeStableName' x)@ but it also forces
88 -- evaluation of @(x)@ to ensure that the 'StableName' is correct first time,
89 -- which avoids to produce a tree bigger than needed.
91 -- Note that this function uses 'unsafePerformIO' instead of returning in 'IO',
92 -- this is apparently required to avoid infinite loops due to unstable 'StableName'
93 -- in compiled code, and sometimes also in ghci.
95 -- Note that maybe [pseq should be used here](https://gitlab.haskell.org/ghc/ghc/-/issues/2916).
96 makeSharingName :: a -> SharingName
97 makeSharingName !x = SharingName $ unsafePerformIO $ makeStableName x
99 instance Eq SharingName where
100 SharingName x == SharingName y = eqStableName x y
101 instance Hashable SharingName where
102 hash (SharingName n) = hashStableName n
103 hashWithSalt salt (SharingName n) = hashWithSalt salt n
105 instance Show SharingName where
106 showsPrec _ (SharingName n) = showHex (I# (unsafeCoerce# n))
109 -- * Type 'ObserveSharing'
110 newtype ObserveSharing letName repr a = ObserveSharing { unObserveSharing ::
111 MT.ReaderT (HashSet SharingName)
112 (MT.State (ObserveSharingState letName))
113 (FinalizeSharing letName repr a) }
115 -- | Interpreter detecting some (Haskell embedded) @let@ definitions used at
116 -- least once and/or recursively, in order to replace them
117 -- with the 'def' and 'ref' combinators.
118 -- See [Type-safe observable sharing in Haskell](https://doi.org/10.1145/1596638.1596653)
120 -- Beware not to apply 'observeSharing' more than once on the same term
121 -- otherwise some 'shareable' introduced by the first call
122 -- would be removed by the second call.
127 ObserveSharing letName repr a ->
128 WithSharing letName repr a
129 observeSharing (ObserveSharing m) =
130 let (fs, st) = MT.runReaderT m mempty `MT.runState`
132 { oss_refs = HM.empty
133 , oss_recs = HS.empty
135 let refs = HS.fromList $
136 (`foldMap` oss_refs st) $ (\(letName, refCount) ->
137 if refCount > 0 then [letName] else []) in
138 --trace (show refs) $
140 (`MT.runReaderT` refs) $
144 data SomeLet repr = forall a. SomeLet (repr a)
146 -- ** Type 'WithSharing'
147 type WithSharing letName repr a =
148 (repr a, HM.HashMap letName (SomeLet repr))
150 -- * Type 'WithSharing'
151 data WithSharing letName repr a = WithSharing
152 { lets :: HM.HashMap letName (SomeLet repr)
156 (forall v. repr v -> repr v) ->
157 WithSharing letName repr a ->
158 WithSharing letName repr a
159 mapWithSharing f ws = WithSharing
160 { lets = (\(SomeLet repr) -> SomeLet (f repr)) <$> lets ws
165 -- ** Type 'ObserveSharingState'
166 data ObserveSharingState letName = ObserveSharingState
167 { oss_refs :: HashMap SharingName (letName, Int)
168 , oss_recs :: HashSet SharingName
169 -- ^ TODO: unused so far, will it be useful somewhere at a later stage?
172 observeSharingNode ::
176 Letable letName repr =>
177 MakeLetName letName =>
178 ObserveSharing letName repr a ->
179 ObserveSharing letName repr a
180 observeSharingNode (ObserveSharing m) = ObserveSharing $ do
181 let nodeName = makeSharingName m
183 ((letName, before), preds) <- getCompose $ HM.alterF (\before ->
184 Compose $ case before of
186 let letName = unsafePerformIO $ makeLetName nodeName
187 return ((letName, before), Just (letName, 0))
188 Just (letName, refCount) -> do
189 return ((letName, before), Just (letName, refCount + 1))
190 ) nodeName (oss_refs st)
191 parentNames <- MT.ask
192 if nodeName `HS.member` parentNames
196 , oss_recs = HS.insert nodeName (oss_recs st)
198 return $ ref True letName
200 MT.lift $ MT.put st{ oss_refs = preds }
202 then MT.local (HS.insert nodeName) (shareable letName <$> m)
203 else return $ ref False letName
205 type instance Derived (ObserveSharing letName repr) = FinalizeSharing letName repr
207 ( Letable letName repr
208 , MakeLetName letName
212 ) => LiftDerived (ObserveSharing letName repr) where
213 liftDerived = observeSharingNode . ObserveSharing . return
215 ( Letable letName repr
216 , MakeLetName letName
220 ) => LiftDerived1 (ObserveSharing letName repr) where
221 liftDerived1 f x = observeSharingNode $ ObserveSharing $
222 f <$> unObserveSharing x
224 ( Letable letName repr
225 , MakeLetName letName
229 ) => LiftDerived2 (ObserveSharing letName repr) where
230 liftDerived2 f x y = observeSharingNode $ ObserveSharing $
231 f <$> unObserveSharing x
232 <*> unObserveSharing y
234 ( Letable letName repr
235 , MakeLetName letName
239 ) => LiftDerived3 (ObserveSharing letName repr) where
240 liftDerived3 f x y z = observeSharingNode $ ObserveSharing $
241 f <$> unObserveSharing x
242 <*> unObserveSharing y
243 <*> unObserveSharing z
244 instance Letable letName (ObserveSharing letName repr) where
245 shareable = error "[BUG]: observeSharing MUST NOT be applied twice"
246 ref = error "[BUG]: observeSharing MUST NOT be applied twice"
247 instance Letsable letName (ObserveSharing letName repr) where
248 lets = error "[BUG]: observeSharing MUST NOT be applied twice"
250 -- * Type 'FinalizeSharing'
251 -- | Remove 'shareable' when non-recursive or unused
252 -- or replace it by 'ref', moving 'def' a top.
253 newtype FinalizeSharing letName repr a = FinalizeSharing { unFinalizeSharing ::
254 MT.ReaderT (HS.HashSet letName)
255 (MT.Writer (LetBindings letName repr))
258 -- ** Type 'LetBindings'
259 type LetBindings letName repr = HM.HashMap letName (SomeLet repr)
261 type instance Derived (FinalizeSharing _letName repr) = repr
265 ) => LiftDerived (FinalizeSharing letName repr) where
266 liftDerived = FinalizeSharing . pure
270 ) => LiftDerived1 (FinalizeSharing letName repr) where
271 liftDerived1 f x = FinalizeSharing $ f <$> unFinalizeSharing x
275 ) => LiftDerived2 (FinalizeSharing letName repr) where
276 liftDerived2 f x y = FinalizeSharing $
277 f <$> unFinalizeSharing x
278 <*> unFinalizeSharing y
282 ) => LiftDerived3 (FinalizeSharing letName repr) where
283 liftDerived3 f x y z = FinalizeSharing $
284 f <$> unFinalizeSharing x
285 <*> unFinalizeSharing y
286 <*> unFinalizeSharing z
288 ( Letable letName repr
292 ) => Letable letName (FinalizeSharing letName repr) where
293 shareable name x = FinalizeSharing $ do
295 if name `HS.member` refs
296 -- This 'shareable' is 'ref'erenced, move it into the result,
297 -- to put it in scope even when some 'ref' to it exists outside of 'x'
298 -- (which can happen when a sub-expression is shared),
299 -- and replace it by a 'ref'.
301 let (repr, defs) = MT.runWriter $ MT.runReaderT (unFinalizeSharing x) refs
302 MT.lift $ MT.tell $ HM.insert name (SomeLet repr) defs
303 return $ ref False name
304 -- Remove 'shareable'.
308 -- * Class 'Letsable'
309 class Letsable letName repr where
310 -- | @('lets' defs x)@ let-binds @(defs)@ in @(x)@.
311 lets :: LetBindings letName repr -> repr a -> repr a
312 lets defs = liftDerived1 (lets ((\(SomeLet val) -> SomeLet (derive val)) <$> defs))
315 FromDerived1 (Letsable letName) repr =>
316 LetBindings letName repr -> repr a -> repr a
318 -- | Not used but can be written nonetheless.
320 ( Letsable letName repr
324 ) => Letsable letName (FinalizeSharing letName repr) where
325 lets defs x = FinalizeSharing $ do
326 ds <- traverse (\(SomeLet v) -> do
327 r <- unFinalizeSharing v