on master help sign in
WIP: runtime type system
commitcbf399caba032935abf3145481c5e76b758265e6 authorAgentM <agentm@themactionfaction.com> dateSat 6 Feb 2016, 18:54 (11 years ago) parent14073e8
13 files changed+152−15
ProjectM36/Atom.hs+12−1 ProjectM36/AtomType.hs+23 ProjectM36/Base.hs+37−4 ProjectM36/Client.hs+10−4 ProjectM36/DatabaseContext.hs+2−1 ProjectM36/Error.hs+3 ProjectM36/Relation.hs+1 ProjectM36/Relation/Show/CSV.hs+3−2 ProjectM36/Relation/Show/Term.hs+1 ProjectM36/RelationalExpression.hs+34−3 TutorialD/Interpreter/DatabaseExpr.hs+21 TutorialD/Interpreter/RODatabaseContextOperator.hs+4 project-m36.cabal+1
ProjectM36/Atom.hs+12−1
@@ -8,11 +8,13 @@ import qualified Data.Text as T
88import Data.Time.Calendar (Day)
99import Data.Time.Clock
1010import Data.ByteString (ByteString)
11+import qualified Data.Map as M
1112
1213relationForAtom :: Atom -> Either RelationalError Relation
1314relationForAtom (Atom atom) = case cast atom of
1415 Just rel@(Relation _ _) -> Right rel
1516 Nothing -> Left $ AttributeIsNotRelationValuedError ""
17+relationForAtom (ConstructedAtom _ _ _) = Left $ AttributeIsNotRelationValuedError ""
1618
1719atomTypeForProxy :: (Atomable a) => Proxy a -> AtomType
1820atomTypeForProxy prox = AtomType $ CTR (typeRep prox)
@@ -70,11 +72,20 @@ intAtom i = Atom i
7072
7173--not safe to use without upfront type-checking
7274castRelation :: Atom -> Relation
75+castRelation (ConstructedAtom _ _ _) = error "castRelation attempt on ConstructedAtom"
7376castRelation (Atom atom) = case cast atom of
7477 Just rel -> rel
75− Nothing -> error "castRelation failure"
78+ Nothing -> error "castRelation attempt on non-relation-typed Atom"
7679
7780unsafeCast :: (Atomable a) => Atom -> a
81+unsafeCast (ConstructedAtom _ _ _) = error "unsafeCast attempt on ConstructedAtom"
7882unsafeCast (Atom atom) = case cast atom of
7983 Just x -> x
8084 Nothing -> error "unsafeCast failed"
85+
86+emptyAtomConstructor :: AtomConstructor
87+emptyAtomConstructor = AtomConstructor M.empty
88+
89+basicAtomTypes :: AtomTypes
90+basicAtomTypes = M.fromList [("Int", (intAtomType, emptyAtomConstructor))]
91+
ProjectM36/AtomType.hsnew file+23
@@ -0,0 +1,23 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module ProjectM36.AtomType where
3+import ProjectM36.Base
4+import ProjectM36.Relation
5+import qualified Data.Text as T
6+import ProjectM36.Error
7+import ProjectM36.Attribute
8+import ProjectM36.Atom
9+import qualified Data.Map as M
10+import qualified Data.Vector as V
11+
12+atomTypesAsRelation :: AtomTypes -> Either RelationalError Relation
13+atomTypesAsRelation aTypes = mkRelationFromTuples attrs tuples
14+ where
15+ attrs = attributesFromList [Attribute "TypeConstructor" stringAtomType,
16+ Attribute "DataConstructors" dConsType]
17+ subAttrs = attributesFromList [Attribute "DataConstructor" stringAtomType]
18+ dConsType = RelationAtomType subAttrs
19+ tuples = map mkTypeConsDescription (M.toList aTypes)
20+ mkTypeConsDescription (tConsName, (_, AtomConstructor dConsMap)) = RelationTuple attrs (V.fromList [stringAtom tConsName, mkDataConsRelation dConsMap])
21+ mkDataConsRelation dConsMap = case mkRelationFromTuples subAttrs $ map (\(dConsName, dConsTypeNames) -> RelationTuple subAttrs (V.singleton $ T.intercalate " " dConsName:dConsTypeNames)) $ M.toList dConsMap of
22+ Left err -> error "mkRelationFromTuples pooped"
23+ Right rel -> Atom rel
ProjectM36/Base.hs+37−4
@@ -56,22 +56,30 @@ instance Atomable Relation
5656instance (Atomable a) => Atomable (Maybe a)
5757
5858-- | Database atoms are the smallest, undecomposable units of a tuple. Common examples are integers, text, or unique identity keys.
59−data Atom = forall a. Atomable a => Atom a
60−
59+data Atom = forall a. Atomable a => Atom a |
60+ ConstructedAtom DataConstructorName AtomType [Atom]
61+
6162instance Eq Atom where
6263 (Atom a) == (Atom b) = Just a == cast b
64+ (ConstructedAtom dConsNameA aTypeA atomListA) == (ConstructedAtom dConsNameB aTypeB atomListB) = aTypeA == aTypeB &&
65+ atomListA == atomListB &&
66+ dConsNameA == dConsNameB
67+ _ == _ = False
6368
6469instance Show Atom where
6570 show (Atom atom) = "Atom " ++ show atom
71+ show (ConstructedAtom dConsName _ atomList) = unpack dConsName ++ " " ++ L.concat (L.intersperse " " (map show atomList))
6672
6773instance Binary Atom where
6874 put atom@(Atom val) = put (atomTypeForAtom atom) >> put val
75+ put (ConstructedAtom dConsName aType atomList) = put aType >> put dConsName >> put atomList
6976 get = do
7077 atomtype <- get
7178 --not-so-great for this to be hardcoded here- it would be nicer to have a dispatch table linked to the instances themselves
7279 --http://stackoverflow.com/questions/8101067/binary-instance-for-an-existential
7380 case atomtype of
7481 AnyAtomType -> error "unsupported serialization AnyAtomType"
82+ caType@(ConstructedAtomType _) -> ConstructedAtom <$> (get :: Get DataConstructorName) <*> pure caType <*> (get :: Get [Atom]) --we really should verify the type with the atoms
7583 (RelationAtomType _) -> Atom <$> (get :: Get Relation)
7684 (AtomType cTypeRep) -> if unCTR cTypeRep == typeRep (Proxy :: Proxy Int) then
7785 Atom <$> (get :: Get Int)
@@ -92,15 +100,19 @@ instance Binary Atom where
92100
93101instance NFData Atom where
94102 rnf !(Atom a) = rnf a
103+ rnf !(ConstructedAtom dConsName aType atomList) = rnf (dConsName, atomList, aType)
95104
96105instance Hashable Atom where
97106 hashWithSalt salt (Atom a) = hashWithSalt salt a
107+ hashWithSalt salt (ConstructedAtom dConsName _ atomList) = salt `hashWithSalt` atomList
108+ `hashWithSalt` dConsName --AtomType is not hashable
98109
99110-- | Return the type of an 'Atom'.
100111atomTypeForAtom :: Atom -> AtomType
101112atomTypeForAtom (Atom atom) = case cast atom of
102113 Just (Relation attrs _) -> RelationAtomType attrs
103114 Nothing -> AtomType $ CTR (typeOf atom)
115+atomTypeForAtom (ConstructedAtom _ aType _) = aType
104116
105117instance Binary UTCTime where
106118 put utc = put $ toRational (utcTimeToPOSIXSeconds utc)
@@ -113,9 +125,12 @@ instance Binary Day where
113125 get = do
114126 (y,m,d) <- get :: Get (Integer, Int, Int)
115127 return (fromGregorian y m d)
116−
128+
129+-- I suspect the definition of ConstructedAtomType with its name alone is insufficient to disambiguate the cases; for example, one could create a type named X, remove a type named X, and re-add it using different constructors. However, as long as requests are served from only one DatabaseContext at-a-time, the type name is unambiguous. This will become a problem for time-travel, however.
130+-- | The AtomType must uniquely identify the type of a atom.
117131data AtomType = AtomType ConcreteTypeRep |
118132 RelationAtomType Attributes |
133+ ConstructedAtomType TypeConstructorName |
119134 AnyAtomType --wildcard used in Atom Functions
120135 deriving (Eq,NFData,Generic,Binary,Show)
121136
@@ -124,6 +139,7 @@ isRelationAtomType :: AtomType -> Bool
124139isRelationAtomType (RelationAtomType _) = True
125140isRelationAtomType _ = False
126141
142+-- | The AttributeName is the name of an attribute in a relation.
127143type AttributeName = StringType
128144
129145-- | A relation's type is composed of attribute names and types.
@@ -256,12 +272,15 @@ data Notification = Notification {
256272 }
257273 deriving (Show, Eq, Binary, Generic)
258274
275+type AtomTypes = M.Map TypeConstructorName (AtomType, AtomConstructor)
276+
259277-- | The DatabaseContext is a snapshot of a database's evolving state and contains everything a database client can change over time.
260278data DatabaseContext = DatabaseContext {
261279 inclusionDependencies :: M.Map IncDepName InclusionDependency,
262280 relationVariables :: M.Map RelVarName Relation,
263281 atomFunctions :: AtomFunctions,
264− notifications :: Notifications
282+ notifications :: Notifications,
283+ atomTypes :: AtomTypes
265284 } deriving (Show, Generic)
266285
267286type IncDepName = StringType
@@ -285,10 +304,24 @@ data DatabaseExpr where
285304
286305 AddNotification :: NotificationName -> RelationalExpr -> RelationalExpr -> DatabaseExpr
287306 RemoveNotification :: NotificationName -> DatabaseExpr
307+
308+ -- this may be generalized to AddAtomFunction in the future
309+ AddAtomConstructor :: TypeConstructorName -> AtomConstructor -> DatabaseExpr
310+ RemoveAtomConstructor :: TypeConstructorName -> DatabaseExpr
311+
312+ -- to implement this, I likely need to implement a DSL for constructing arbitrary functions to operate on atoms at runtime
313+ --AddAtomFunction :: AtomFunction -> DatabaseExpr
314+ --RemoveAtomFunction :: AtomFunctionName -> DatabaseExpr
288315
289316 MultipleExpr :: [DatabaseExpr] -> DatabaseExpr
290317 deriving (Show, Eq, Binary, Generic)
291318
319+type TypeConstructorName = StringType
320+type DataConstructorName = StringType
321+type AtomTypeName = StringType
322+
323+data AtomConstructor = AtomConstructor (M.Map DataConstructorName [AtomTypeName]) deriving (Eq, Show, Binary, Generic)
324+
292325type DatabaseState a = State DatabaseContext a
293326
294327-- | Restriction predicate are boolean algebra components which, when composed, indicate whether or not a tuple should be retained during a restriction (filtering) operation.
ProjectM36/Client.hs+10−4
@@ -56,7 +56,8 @@ import ProjectM36.TransactionGraph.Persist
5656import ProjectM36.Attribute
5757import ProjectM36.Persist (DiskSync(..))
5858import ProjectM36.Notifications
59−import ProjectM36.Server.RemoteCallTypes
59+import ProjectM36.Server.RemoteCallTypes
60+import ProjectM36.AtomType (atomTypesAsRelation)
6061import Network.Transport.TCP (createTransport, defaultTCPParameters, encodeEndPointAddress)
6162import Control.Distributed.Process.Node (newLocalNode, initRemoteTable, runProcess, LocalNode, forkProcess)
6263import Control.Distributed.Process.Extras.Internal.Types (whereisRemote)
@@ -504,6 +505,11 @@ headName sessionId (InProcessConnection _ _ sessions graphTvar) = do
504505 atomically (headNameSTM_ sessionId sessions graphTvar)
505506headName sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteHeadName sessionId)
506507
507−
508−
509−
508+atomTypesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation)
509+atomTypesAsRelation sessionId (InProcessConnection _ _ sessions) = do
510+ atomically $ do
511+ eSession <- sessionForSessionId sessionId sessions
512+ case eSession of
513+ Left err -> pure (Left err)
514+ Right (Session discon) -> do
515+
ProjectM36/DatabaseContext.hs+2−1
@@ -7,7 +7,8 @@ empty :: DatabaseContext
77empty = DatabaseContext { inclusionDependencies = M.empty,
88 relationVariables = M.empty,
99 notifications = M.empty,
10− atomFunctions = HS.empty }
10+ atomFunctions = HS.empty,
11+ atomTypes = M.empty }
1112
1213-- | convert an existing database context into its constituent expression.
1314databaseContextAsDatabaseContextExpr :: DatabaseContext -> DatabaseExpr
ProjectM36/Error.hs+3
@@ -33,6 +33,9 @@ data RelationalError = NoSuchAttributeNamesError (S.Set AttributeName)
3333 | NoSuchSession U.UUID
3434 | NoSuchTupleExprFunctionError AtomFunctionName
3535 | AtomTypeMismatchError AtomType AtomType
36+ | AtomTypeNameInUseError AtomTypeName
37+ | AtomTypeNameNotInUseError AtomTypeName
38+ | InvalidAtomTypeName AtomTypeName
3639 | AtomTypeNotSupported AttributeName --used by persistent driver
3740 | AtomOperatorNotSupported T.Text --used by persistent driver
3841 | EmptyTuplesError -- used by persistent driver
ProjectM36/Relation.hs+1
@@ -271,6 +271,7 @@ matrixRelation attributeCount tupleCount = do
271271
272272
273273castInt :: Atom -> Int
274+castInt (ConstructedAtom _ _ _) = error "castInt attempt on ConstructedAtom"
274275castInt (Atom atom) = case cast atom of
275276 Just i -> i
276277 Nothing -> error "int cast failure" -- the type checker must ensure this can never occur
ProjectM36/Relation/Show/CSV.hs+3−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE OverloadedStrings #-}
12{-# OPTIONS_GHC -fno-warn-orphans #-}
23module ProjectM36.Relation.Show.CSV where
34import ProjectM36.Base
@@ -8,8 +9,7 @@ import qualified Data.ByteString.Lazy as BS
89import qualified Data.Vector as V
910import ProjectM36.Error
1011import qualified Data.Text.Encoding as TE
11−
12−
12+import qualified Data.Text as T
1313
1414--spit out error for relations without attributes (since relTrue and relFalse cannot be distinguished then as CSV) and for relations with relation-valued attributes
1515relationAsCSV :: Relation -> Either RelationalError BS.ByteString
@@ -39,4 +39,5 @@ instance DefaultOrdered RecordRelationTuple where
3939newtype RecordAtom = RecordAtom {unAtom :: Atom}
4040
4141instance ToField RecordAtom where
42+ toField (RecordAtom (ConstructedAtom dConsName _ atomList)) = TE.encodeUtf8 $ dConsName `T.append` T.intercalate " " (map (\(Atom x) -> toText x) atomList)
4243 toField (RecordAtom (Atom atomVal)) = (TE.encodeUtf8 . toText) atomVal
ProjectM36/Relation/Show/Term.hs+1
@@ -95,6 +95,7 @@ showAtom :: Atom -> StringType
9595showAtom (Atom atom) = case cast atom of
9696 Just rel -> renderTable $ relationAsTable rel
9797 Nothing -> toText atom
98+showAtom (ConstructedAtom dConsName _ atoms) = dConsName `T.append` " " `T.append` T.concat (L.intersperse " " (map showAtom atoms))
9899
99100renderTable :: Table -> StringType
100101renderTable table = renderHeader table (fst cellLocs) `T.append` renderBody (snd table) cellLocs
ProjectM36/RelationalExpression.hs+34−3
@@ -16,6 +16,8 @@ import qualified Data.Set as S
1616import Control.Monad.State hiding (join)
1717import Data.Maybe
1818import Data.Either
19+import Data.Char (isUpper)
20+import qualified Data.Text as T
1921
2022--relvar state is needed in evaluation of relational expression but only as read-only in order to extract current relvar values
2123evalRelationalExpr :: RelationalExpr -> DatabaseState (Either RelationalError Relation)
@@ -117,7 +119,8 @@ emptyDatabaseContext :: DatabaseContext
117119emptyDatabaseContext = DatabaseContext { inclusionDependencies = M.empty,
118120 relationVariables = M.empty,
119121 atomFunctions = HS.empty,
120− notifications = M.empty
122+ notifications = M.empty,
123+ atomTypes = M.empty
121124 }
122125
123126basicDatabaseContext :: DatabaseContext
@@ -125,14 +128,16 @@ basicDatabaseContext = DatabaseContext { inclusionDependencies = M.empty,
125128 relationVariables = M.fromList [("true", relationTrue),
126129 ("false", relationFalse)],
127130 atomFunctions = basicAtomFunctions,
128− notifications = M.empty
131+ notifications = M.empty,
132+ atomTypes = basicAtomTypes
129133 }
130134
131135dateExamples :: DatabaseContext
132136dateExamples = DatabaseContext { inclusionDependencies = dateIncDeps,
133137 relationVariables = M.union (relationVariables basicDatabaseContext) dateRelVars,
134138 notifications = M.empty,
135− atomFunctions = basicAtomFunctions}
139+ atomFunctions = basicAtomFunctions,
140+ atomTypes = basicAtomTypes }
136141 where
137142 dateRelVars = M.fromList [("S", suppliers),
138143 ("P", products),
@@ -327,6 +332,32 @@ evalContextExpr (RemoveNotification notName) = do
327332 put $ currentContext { notifications = newNotifications }
328333 return Nothing
329334
335+-- | Adds type and data constructors to the database context.
336+-- validate that the type *and* constructor names are unique! not yet implemented!
337+evalContextExpr (AddAtomConstructor tConsName atomConstructor) = do
338+ currentContext <- get
339+ let oldAtomTypes = atomTypes currentContext
340+ if T.length tConsName < 1 || not (isUpper (T.head tConsName)) then
341+ pure $ Just (InvalidAtomTypeName tConsName)
342+ else if M.member tConsName oldAtomTypes then
343+ pure $ Just (AtomTypeNameInUseError tConsName)
344+ else do
345+ let newAtomTypes = M.insert tConsName newType oldAtomTypes
346+ newType = (ConstructedAtomType tConsName, atomConstructor)
347+ put $ currentContext { atomTypes = newAtomTypes }
348+ pure Nothing
349+
350+-- | Removing the atom constructor prevents new atoms of the type from being created. Existing atoms of the type remain. Thus, the atomTypes list in the DatabaseContext need not be all-inclusive.
351+evalContextExpr (RemoveAtomConstructor tConsName) = do
352+ currentContext <- get
353+ let oldAtomTypes = atomTypes currentContext
354+ if M.notMember tConsName oldAtomTypes then
355+ pure $ Just (AtomTypeNameNotInUseError tConsName)
356+ else do
357+ let newAtomTypes = M.delete tConsName oldAtomTypes
358+ put $ currentContext { atomTypes = newAtomTypes }
359+ pure Nothing
360+
330361evalContextExpr (MultipleExpr exprs) = do
331362 --the multiple expressions must pass the same context around- not the old unmodified context
332363 evald <- forM exprs evalContextExpr
TutorialD/Interpreter/DatabaseExpr.hs+21
@@ -26,6 +26,8 @@ databaseExprP = insertP
2626 <|> assignP
2727 <|> addNotificationP
2828 <|> removeNotificationP
29+ <|> addAtomConstructorP
30+ <|> removeAtomConstructorP
2931
3032assignP :: Parser DatabaseExpr
3133assignP = do
@@ -124,6 +126,25 @@ removeNotificationP = do
124126 notName <- identifier
125127 return $ RemoveNotification (T.pack notName)
126128
129+-- | data Hair = Bald | Color Text
130+addAtomConstructorP :: Parser DatabaseExpr
131+addAtomConstructorP = do
132+ reserved "data"
133+ typeConstructorName <- identifier
134+ reservedOp "="
135+ dataConstructors <- sepBy1 dataConstructorP comma
136+ let aCons = AtomConstructor (M.fromList dataConstructors)
137+ pure (AddAtomConstructor (T.pack typeConstructorName) aCons)
138+
139+dataConstructorP :: Parser (DataConstructorName, [AtomTypeName])
140+dataConstructorP = do
141+ dConsName <- identifier
142+ atomTypeNames <- sepBy (liftM T.pack identifier) spaces
143+ pure (T.pack dConsName, atomTypeNames)
144+
145+removeAtomConstructorP :: Parser DatabaseExpr
146+removeAtomConstructorP = RemoveAtomConstructor <$> liftM T.pack identifier
147+
127148databaseExprOpP :: Parser DatabaseExpr
128149databaseExprOpP = multipleDatabaseExprP
129150
TutorialD/Interpreter/RODatabaseContextOperator.hs+4
@@ -21,6 +21,7 @@ data RODatabaseContextOperator where
2121 ShowRelationType :: RelationalExpr -> RODatabaseContextOperator
2222 ShowConstraint :: StringType -> RODatabaseContextOperator
2323 ShowPlan :: DatabaseExpr -> RODatabaseContextOperator
24+ ShowTypes :: RODatabaseContextOperator
2425 Quit :: RODatabaseContextOperator
2526 deriving (Show)
2627
@@ -42,6 +43,9 @@ showPlanP = do
4243 expr <- databaseExprP
4344 return $ ShowPlan expr
4445
46+showTypesP :: Parser RODatabaseContextOperator
47+showTypesP = reserved ":showtypes" >> pure ShowTypes
48+
4549quitP :: Parser RODatabaseContextOperator
4650quitP = do
4751 reservedOp ":quit"
project-m36.cabal+1
@@ -88,6 +88,7 @@ Library
8888 ProjectM36.Relation.Parse.CSV,
8989 ProjectM36.Client,
9090 ProjectM36.Persist,
91+ ProjectM36.AtomType,
9192 Database.Persist.ProjectM36
9293 GHC-Options: -Wall -rtsopts
9394 C-sources: ProjectM36/DirectoryFsync.c