Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
8192fb7
Spec out new Record type with pattern matching
ChrisPenner Apr 15, 2025
7545cbb
Add missing cases for new runtime Records
ChrisPenner Jan 22, 2026
8f1fe35
Checkpoint
ChrisPenner Jan 22, 2026
b275831
Remove FRec from functions
ChrisPenner Jan 22, 2026
c59f505
WIP
ChrisPenner Jan 22, 2026
f94473c
WIP
ChrisPenner Jan 23, 2026
0d2ed95
Compiling, but with a lot of "error"s
ChrisPenner Jan 23, 2026
7d3e2a0
Remove Reference from record term ast
ChrisPenner Jan 23, 2026
74f2546
More switching Record to not have a reference
ChrisPenner Jan 23, 2026
eea99c9
Finish removing Reference from Record, and swap it to use a Map
ChrisPenner Jan 23, 2026
7b74da8
Wire in record literal parser
ChrisPenner Jan 23, 2026
ff6a989
Record parser
ChrisPenner Jan 23, 2026
19ea4ff
Add Record Type F
ChrisPenner Jan 23, 2026
6d4feae
Record type hashing
ChrisPenner Jan 23, 2026
e094006
Attempt to do Kind Inference for record fields
ChrisPenner Jan 23, 2026
6489447
Fix synhashing of records
ChrisPenner Jan 23, 2026
de1c2b2
Compiling after adding record type F
ChrisPenner Jan 23, 2026
ea7bb13
Implement record type synthesis
ChrisPenner Jan 23, 2026
c6167ed
Better record type printing
ChrisPenner Jan 23, 2026
28b8b29
Typechecking is _running_, but incorrect
ChrisPenner Jan 23, 2026
7b3583d
Implement unification on Records
ChrisPenner Jan 23, 2026
394da37
More missing field error messages
ChrisPenner Jan 24, 2026
b7e5bf9
Better error messages
ChrisPenner Jan 24, 2026
8e3316d
Fill out Record packing
ChrisPenner Jan 26, 2026
bf9d89e
coalesce wanted in Records
ChrisPenner Jan 26, 2026
ce16035
Switch from BLDR to a Record Pack func app
ChrisPenner Jan 26, 2026
249d36c
Moar serialization
ChrisPenner Jan 27, 2026
369c93d
More threading in rec Schema discovery
ChrisPenner Jan 27, 2026
41f3691
Add record schema numbering
ChrisPenner Jan 28, 2026
07e6db4
Murmur hashing for record values
ChrisPenner Jan 28, 2026
6d2dc86
Record v5 serialization
ChrisPenner Jan 28, 2026
357785b
Fix RecordC vs RecordG
ChrisPenner Jan 28, 2026
c4d2727
Pass in rec schema lookups
ChrisPenner Jan 28, 2026
b385c4f
Add BiMap to utils
ChrisPenner Jan 28, 2026
10b0988
Use BiMap
ChrisPenner Jan 28, 2026
39a42be
Simplify error handling for runtime exceptions
ChrisPenner Jan 28, 2026
f501c96
Fix term printer for records
ChrisPenner Jan 28, 2026
5059500
Record Pattern parser
ChrisPenner Jan 28, 2026
c002069
First attempt at Record field desugaring
ChrisPenner Jan 28, 2026
c6b7654
Fix (?) record pattern match desugaring
ChrisPenner Jan 28, 2026
bd58726
Skip pattern match constraint solving for now
ChrisPenner Jan 28, 2026
a31db74
Fix up LSP queries
ChrisPenner Jan 29, 2026
eff2dfc
Implement some pattern match type-checking errors
ChrisPenner Jan 29, 2026
8760d9c
Implement record pattern pretty printing
ChrisPenner Jan 29, 2026
c1a4c03
Don't depend on pattern scrutinee type before we solve it
ChrisPenner Jan 29, 2026
de4c709
Add some notes
ChrisPenner Jan 30, 2026
9356719
Implement record type parser
ChrisPenner Jan 30, 2026
1903f63
Add completeness pragma for Effect''
ChrisPenner Feb 2, 2026
6b41c33
Implement instantiateL and equate0 for records
ChrisPenner Feb 2, 2026
988b3f2
Notes from typechecking paper
ChrisPenner Feb 2, 2026
546a430
instantiateR for records
ChrisPenner Feb 2, 2026
1cea3b8
Add a few missing Record cases in typechecker
ChrisPenner Feb 3, 2026
732f78f
Circumvent pattern-match coverage checker. Remove this commit and fix it
ChrisPenner Feb 3, 2026
d19d20f
WIP pattern match runtime
ChrisPenner Feb 3, 2026
0dfb37b
Add ANF cases for record pattern-matching
ChrisPenner Feb 4, 2026
a98d0df
More ANF record pattern matching
ChrisPenner Feb 4, 2026
cf480e5
Add RecUnpack instr
ChrisPenner Feb 4, 2026
289bc07
Simple runtime pattern matching is working
ChrisPenner Feb 5, 2026
6ceb411
Working pattern unpacking for fully specified record patterns
ChrisPenner Feb 5, 2026
c3d06ec
Remove old debug statements
ChrisPenner Feb 5, 2026
b2fcefd
Add unexpected field error
ChrisPenner Feb 5, 2026
577fd3d
Store actual HashMap in runtime Closure
ChrisPenner Feb 5, 2026
7ed9786
Attempt to fix lsp errors
ChrisPenner Feb 5, 2026
999eb87
Add new records transcript
ChrisPenner Feb 5, 2026
f278efc
Update transcripts
ChrisPenner Feb 5, 2026
dbd4739
Implement `{field : f | ...}` field subset typechecking
ChrisPenner Feb 5, 2026
65c331b
Fix imports
ChrisPenner Feb 19, 2026
d7f1156
Rewrite Emit monad
ChrisPenner Feb 19, 2026
2bef918
Finish rewriting Emit monad
ChrisPenner Feb 20, 2026
ca7483a
Start threading RFM initialization
ChrisPenner Feb 20, 2026
d7fe4a9
Better RecordFieldMapping builder
ChrisPenner Feb 24, 2026
20eb2de
WIP
ChrisPenner Feb 24, 2026
3bdafe5
Compiling with field-refs
ChrisPenner Feb 24, 2026
4cd43b6
Working again, now with FieldRefs
ChrisPenner Feb 24, 2026
f17c0d0
Fix badly recursive case in term printer
ChrisPenner Feb 26, 2026
cf4720a
Fix up groupCases
ChrisPenner Feb 26, 2026
24be717
Fix folding over vs in record literal
ChrisPenner Feb 26, 2026
54d7643
automatically run ormolu
ChrisPenner Feb 26, 2026
28e0ff7
Transcript updates
ChrisPenner Sep 28, 2026
1684501
Transcript updates
ChrisPenner Sep 28, 2026
2fe90e6
Implement Universal compare on records
ChrisPenner Sep 28, 2026
09ef8d2
PR Cleanup
ChrisPenner Sep 28, 2026
434d9c5
Update universals in transcript
ChrisPenner Sep 28, 2026
67c110f
Records: fix ANF tag deserialization and delimit record hashes
ChrisPenner Sep 28, 2026
1ea4bf1
Records: fix nested record patterns and correct record type errors
ChrisPenner Sep 28, 2026
90d0f63
Records: support refutable patterns inside record patterns
ChrisPenner Sep 28, 2026
6f98119
Records: reject duplicate field names
ChrisPenner Sep 28, 2026
540321d
Records: order record fields by name, and implement value reflection
ChrisPenner Sep 29, 2026
bc84e02
Records: print real field names in runtime errors
ChrisPenner Sep 29, 2026
ff6525a
Records: add field projection with `r@x`
ChrisPenner Sep 29, 2026
248b1eb
Records: support record patterns in `@rewrite case` rules
ChrisPenner Sep 29, 2026
44903e5
Merge trunk into the records branch
ChrisPenner Sep 29, 2026
cbfab33
Records: repair the runtime test suite
ChrisPenner Sep 29, 2026
e44404b
Records: make record patterns compare equal to each other
ChrisPenner Sep 29, 2026
0f01c31
Disable libffi tests on mac, where system libffi doesn't behave.
ChrisPenner Sep 29, 2026
cbc8846
Bump codebase version
ChrisPenner Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/proofs/formatting.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e22453ae22870523f79984c00371b3514967b63f230dfee2297964f1b6c1b251 ab5370655049c955d122d644eb26598258e7998bf91ebc7433f068470a966a9e pass
6fa984cffdeb66869d9e53ccf73166a0d3cbaa2c1e07ded9780652f9c0002d1b ab5370655049c955d122d644eb26598258e7998bf91ebc7433f068470a966a9e pass
2 changes: 1 addition & 1 deletion .github/workflows/proofs/tests.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8309e64b8a084106fe325f756e8e5e7abd81cea1f33b20f335cfb06a83d80eb9 36894b8d3d19e10c74bdc233d752feca155998462033d20e6770eba9f82a10a0 pass
4338a8b7d94500696756f67931282126fc0dd23b8c838efa0599781226d03ce9 36894b8d3d19e10c74bdc233d752feca155998462033d20e6770eba9f82a10a0 pass
2 changes: 1 addition & 1 deletion .github/workflows/proofs/transcripts.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8667add51840b308bc23bf95a9c9c0032ff5486ec285e3d3de38970385912c67 beb4a304f24dcbdf3db627887d7c02e22209258fc25b84ad944267279c7278aa pass
4497d4885947f64a10a617e786b65a989442e48af7e92cdbf2f1ef4eee8daf76 beb4a304f24dcbdf3db627887d7c02e22209258fc25b84ad944267279c7278aa pass
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ v2ToH2Type' mkReference = ABT.transform convertF
V2.Type.Effects a -> H2.TypeEffects a
V2.Type.Forall a -> H2.TypeForall a
V2.Type.IntroOuter a -> H2.TypeIntroOuter a
V2.Type.Record fb fields -> H2.TypeRecord (convertFB fb) fields
convertFB = \case
V2.Type.AllowExtraFields -> H2.AllowExtraFields
V2.Type.RequireExactFields -> H2.RequireExactFields

convertKind :: V2.Kind -> H2.Kind
convertKind = \case
Expand Down Expand Up @@ -179,6 +183,7 @@ v2ToH2Term = ABT.transform convertF
V2.Term.Char c -> H2.TermChar c
V2.Term.Ref r -> H2.TermRef (v2ToH2Reference r)
V2.Term.Constructor r cid -> H2.TermConstructor (v2ToH2Reference r) cid
V2.Term.Record fields -> H2.TermRecord fields
V2.Term.Request r cid -> H2.TermRequest (v2ToH2Reference r) cid
V2.Term.Handle a b -> H2.TermHandle a b
V2.Term.App a b -> H2.TermApp a b
Expand Down Expand Up @@ -210,6 +215,7 @@ v2ToH2Term = ABT.transform convertF
V2.Term.PChar c -> H2.PatternChar () c
V2.Term.PBytes b -> H2.PatternBytes () b
V2.Term.PConstructor r cid ps -> H2.PatternConstructor () (v2ToH2Reference r) cid (convertPattern <$> ps)
V2.Term.PRecord fieldPats -> H2.PatternRecord () (convertPattern <$> fieldPats)
V2.Term.PAs pat -> H2.PatternAs () (convertPattern pat)
V2.Term.PEffectPure pat -> H2.PatternEffectPure () (convertPattern pat)
V2.Term.PEffectBind r conId pats pat -> H2.PatternEffectBind () (v2ToH2Reference r) conId (convertPattern <$> pats) (convertPattern pat)
Expand Down
6 changes: 5 additions & 1 deletion codebase2/codebase-sqlite/U/Codebase/Sqlite/Queries.hs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ type TextPathSegments = [Text]
-- * main squeeze

currentSchemaVersion :: SchemaVersion
currentSchemaVersion = 26
currentSchemaVersion = 27

runCreateSql :: Transaction ()
runCreateSql =
Expand Down Expand Up @@ -2738,6 +2738,7 @@ c2sDecl saveText saveDefn (C.Decl.DataDeclaration dt m b cts) = do
C.Type.Effects es -> pure $ C.Type.Effects es
C.Type.Forall a -> pure $ C.Type.Forall a
C.Type.IntroOuter a -> pure $ C.Type.IntroOuter a
C.Type.Record fb fields -> pure $ C.Type.Record fb fields
done :: (S.Decl.Decl Symbol, (Seq Text, Seq Hash)) -> m (LocalIds' t d, S.Decl.Decl Symbol)
done (decl, (localTextValues, localDefnValues)) = do
textIds <- traverse saveText localTextValues
Expand Down Expand Up @@ -2771,6 +2772,7 @@ c2xTerm saveText saveDefn tm tp =
C.Term.Constructor
<$> bitraverse lookupText lookupDefn typeRef
<*> pure cid
C.Term.Record fields -> pure $ C.Term.Record fields
C.Term.Request typeRef cid ->
C.Term.Request <$> bitraverse lookupText lookupDefn typeRef <*> pure cid
C.Term.Handle a a2 -> pure $ C.Term.Handle a a2
Expand Down Expand Up @@ -2806,6 +2808,7 @@ c2xTerm saveText saveDefn tm tp =
C.Type.Effects es -> pure $ C.Type.Effects es
C.Type.Forall a -> pure $ C.Type.Forall a
C.Type.IntroOuter a -> pure $ C.Type.IntroOuter a
C.Type.Record fb fields -> pure $ C.Type.Record fb fields
goCase ::
forall m w s a.
( MonadState s m,
Expand Down Expand Up @@ -2842,6 +2845,7 @@ c2xTerm saveText saveDefn tm tp =
C.Term.PChar c -> pure $ C.Term.PChar c
C.Term.PBytes b -> pure $ C.Term.PBytes b
C.Term.PConstructor r i ps -> C.Term.PConstructor <$> bitraverse lookupText lookupDefn r <*> pure i <*> traverse goPat ps
C.Term.PRecord fields -> C.Term.PRecord <$> traverse goPat fields
C.Term.PAs p -> C.Term.PAs <$> goPat p
C.Term.PEffectPure p -> C.Term.PEffectPure <$> goPat p
C.Term.PEffectBind r i bindings k -> C.Term.PEffectBind <$> bitraverse lookupText lookupDefn r <*> pure i <*> traverse goPat bindings <*> goPat k
Expand Down
20 changes: 20 additions & 0 deletions codebase2/codebase-sqlite/U/Codebase/Sqlite/Serialization.hs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import Data.Bytes.Put (MonadPut, putByteString, putWord8)
import Data.Bytes.Serial (SerialEndian (serializeBE), deserialize, deserializeBE, serialize)
import Data.Bytes.VarInt (VarInt (VarInt), unVarInt)
import Data.List (elemIndex)
import Data.Map qualified as Map
import Data.Set qualified as Set
import Data.Vector (Vector)
import U.Codebase.Decl (Modifier)
Expand Down Expand Up @@ -281,6 +282,8 @@ putSingleTerm t = putABT putSymbol putUnit putF t
putWord8 20 *> putReferent' putRecursiveReference putReference r
Term.TypeLink r ->
putWord8 21 *> putReference r
Term.Record fields ->
putWord8 22 *> putFoldable (\(name, val) -> putText name *> putChild val) (Map.toList fields)
putMatchCase :: (MonadPut m) => (a -> m ()) -> Term.MatchCase LocalTextId TermFormat.TypeRef a -> m ()
putMatchCase putChild (Term.MatchCase pat guard body) =
putPattern pat *> putMaybe putChild guard *> putChild body
Expand Down Expand Up @@ -317,6 +320,10 @@ putSingleTerm t = putABT putSymbol putUnit putF t
Term.PBytes b ->
let bs = Bytes.toByteString b
in putWord8 14 *> putVarInt (BS.length bs) *> putByteString bs
Term.PRecord fields ->
putWord8 15
*> putFoldable (\(name, pat) -> putText name *> putPattern pat) (Map.toList fields)

putSeqOp :: (MonadPut m) => Term.SeqOp -> m ()
putSeqOp Term.PCons = putWord8 0
putSeqOp Term.PSnoc = putWord8 1
Expand Down Expand Up @@ -368,6 +375,10 @@ getSingleTerm = getABT getSymbol getUnit getF
19 -> Term.Char <$> getChar
20 -> Term.TermLink <$> getReferent
21 -> Term.TypeLink <$> getReference
22 ->
getList
((,) <$> getText <*> getChild)
<&> Term.Record . Map.fromList
tag -> unknownTag "getSingleTerm" tag
where
getReferent :: (MonadGet m) => m (Referent' TermFormat.TermRef TermFormat.TypeRef)
Expand Down Expand Up @@ -406,6 +417,7 @@ getSingleTerm = getABT getSymbol getUnit getF
n <- getVarInt
bs <- getByteString n
pure $ Term.PBytes (Bytes.fromByteString bs)
15 -> Term.PRecord . Map.fromList <$> (getList ((,) <$> getText <*> getPattern))
x -> unknownTag "Pattern" x
where
getSeqOp :: (MonadGet m) => m Term.SeqOp
Expand Down Expand Up @@ -452,6 +464,7 @@ getType getReference = getABT getSymbol getUnit go
5 -> Type.Effects <$> getList getChild
6 -> Type.Forall <$> getChild
7 -> Type.IntroOuter <$> getChild
8 -> Type.Record <$> getEnum @Type.FieldBehavior <*> (Map.fromList <$> getList (getPair getText getChild))
tag -> unknownTag "getType" tag
getKind :: (MonadGet m) => m Kind
getKind =
Expand Down Expand Up @@ -1128,6 +1141,7 @@ putType putReference putVar = putABT putVar putUnit go
Type.Effects es -> putWord8 5 *> putFoldable putChild es
Type.Forall body -> putWord8 6 *> putChild body
Type.IntroOuter body -> putWord8 7 *> putChild body
Type.Record fb fields -> putWord8 8 *> putEnum @Type.FieldBehavior fb *> putFoldable (\(l, t) -> putText l *> putChild t) (Map.toAscList fields)
putKind :: (MonadPut m) => Kind -> m ()
putKind k = case k of
Kind.Star -> putWord8 0
Expand All @@ -1154,3 +1168,9 @@ getMaybe getA =
unknownTag :: (MonadGet m, Show a) => String -> a -> m x
unknownTag msg tag =
fail $ "unknown tag " ++ show tag ++ " while deserializing: " ++ msg

putEnum :: forall e m. (MonadPut m, Enum e) => e -> m ()
putEnum e = putVarInt (fromEnum e)

getEnum :: forall e m. (MonadGet m, Enum e) => m e
getEnum = toEnum <$> getVarInt
1 change: 1 addition & 0 deletions codebase2/codebase/U/Codebase/Decl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ unhashComponent componentHash refToVar m =
Type.Effects as -> ABT.tm () $ Type.Effects as
Type.Forall a -> ABT.tm () $ Type.Forall a
Type.IntroOuter a -> ABT.tm () $ Type.IntroOuter a
Type.Record fb fields -> ABT.tm () $ Type.Record fb fields
5 changes: 5 additions & 0 deletions codebase2/codebase/U/Codebase/Term.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ data F' text termRef typeRef termLink typeLink vt a
| -- First argument identifies the data type,
-- second argument identifies the constructor
Constructor typeRef ConstructorId
| Record (Map Text {- field name -} a {- field value -})
| Request typeRef ConstructorId
| Handle a a
| App a a
Expand Down Expand Up @@ -109,6 +110,7 @@ data Pattern t r
| PChar !Char
| PBytes !Bytes
| PConstructor !r !ConstructorId [Pattern t r]
| PRecord (Map Text (Pattern t r))
| PAs (Pattern t r)
| PEffectPure (Pattern t r)
| PEffectBind !r !ConstructorId [Pattern t r] (Pattern t r)
Expand Down Expand Up @@ -189,6 +191,7 @@ extraMapM ftext ftermRef ftypeRef ftermLink ftypeLink fvt = go'
Char c -> pure $ Char c
Ref r -> Ref <$> ftermRef r
Constructor r cid -> Constructor <$> (ftypeRef r) <*> pure cid
Record fields -> pure $ Record fields
Request r cid -> Request <$> ftypeRef r <*> pure cid
Handle e h -> pure $ Handle e h
App f a -> pure $ App f a
Expand Down Expand Up @@ -224,6 +227,7 @@ rmapPatternM ft fr = go
PChar c -> pure $ PChar c
PBytes b -> pure $ PBytes b
PConstructor r i ps -> PConstructor <$> fr r <*> pure i <*> (traverse go ps)
PRecord fields -> PRecord <$> (traverse go fields)
PAs p -> PAs <$> go p
PEffectPure p -> PEffectPure <$> go p
PEffectBind r i ps p -> PEffectBind <$> fr r <*> pure i <*> traverse go ps <*> go p
Expand Down Expand Up @@ -332,6 +336,7 @@ unhashComponent componentHash refToVar m =
Text t -> ABT.tm () $ Text t
Char c -> ABT.tm () $ Char c
Constructor typeRef conId -> ABT.tm () $ Constructor typeRef conId
Record fields -> ABT.tm () $ Record fields
Request typeRef conId -> ABT.tm () $ Request typeRef conId
Handle e h -> ABT.tm () $ Handle e h
App f a -> ABT.tm () $ App f a
Expand Down
12 changes: 12 additions & 0 deletions codebase2/codebase/U/Codebase/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ type FT = F' Reference
-- | For potentially recursive types, like those in DataDeclaration
type FD = F' (Reference' Text (Maybe Hash))

-- | Whether the record type unifies with types that have _extra_ fields.
-- E.g. subtype (Record _ {a: Int, b: Nat}) (Record AllowExtraFields {a: Int})
-- will succeed, since the former has all the required fields, and extra fields are allowed,
-- but:
-- subtype (Record _ {a: Int, b: Nat}) (Record RequireExactFields {a: Int})
-- fails.
data FieldBehavior
= AllowExtraFields
| RequireExactFields
deriving (Eq, Ord, Show, Enum, Bounded)

data F' r a
= Ref r
| Arrow a a
Expand All @@ -27,6 +38,7 @@ data F' r a
| IntroOuter a -- binder like ∀, used to introduce variables that are
-- bound by outer type signatures, to support scoped type
-- variables
| Record FieldBehavior (Map Text a)
deriving (Foldable, Functor, Eq, Ord, Show, Traversable)

-- | Non-recursive type
Expand Down
3 changes: 3 additions & 0 deletions lib/unison-pretty-printer/src/Unison/Util/ColorText.hs
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,6 @@ defaultColors = \case
ST.Parenthesis -> Nothing
ST.DocDelimiter -> Just Green
ST.DocKeyword -> Just HiCyan
ST.RecordFieldName {} -> Just HiCyan
ST.RecordFieldValueColon -> Just HiPurple
ST.RecordExtraFields -> Just HiPurple
3 changes: 3 additions & 0 deletions lib/unison-pretty-printer/src/Unison/Util/SyntaxText.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ data Element r
| DocDelimiter
| -- the 'source' in @source{…}, etc
DocKeyword
| RecordFieldName Text
| RecordFieldValueColon
| RecordExtraFields
deriving (Eq, Ord, Show, Functor)

syntax :: Element r -> SyntaxText' r -> SyntaxText' r
Expand Down
105 changes: 105 additions & 0 deletions lib/unison-util-relation/src/Unison/Util/BiMap.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module Unison.Util.BiMap
( BiMap (..),
empty,
singleton,
fromList,
fromMap,
toList,
toMapL,
toMapR,
lookupL,
lookupR,
union,
difference,
insert,
deleteL,
deleteR,
keysSetL,
keysSetR,
)
where

import Data.Map qualified as Map
import Data.Set (Set)
import Data.Tuple (swap)

-- | A bidirectional map between keys of type k and values of type v.
data BiMap k v = BiMap
{ forward :: Map.Map k v,
backward :: Map.Map v k
}
deriving (Eq, Ord, Show)

-- | Combine two BiMaps. In case of key or value collisions, the entries from
-- the second BiMap take precedence.
instance (Ord k, Ord v) => Semigroup (BiMap k v) where
BiMap f1 b1 <> BiMap f2 b2 =
BiMap (Map.union f2 f1) (Map.union b2 b1)

instance (Ord k, Ord v) => Monoid (BiMap k v) where
mempty = BiMap Map.empty Map.empty

empty :: (Ord k, Ord v) => BiMap k v
empty = mempty

singleton :: k -> v -> BiMap k v
singleton k v = BiMap (Map.singleton k v) (Map.singleton v k)

fromList :: (Ord k, Ord v) => [(k, v)] -> BiMap k v
fromList kvs =
let forward = Map.fromList kvs
backward = Map.fromList (swap <$> Map.toList forward)
in BiMap {forward, backward}

fromMap :: (Ord k, Ord v) => Map.Map k v -> BiMap k v
fromMap f =
let b = Map.fromList (swap <$> Map.toList f)
in BiMap f b

toList :: BiMap k v -> [(k, v)]
toList (BiMap f _) = Map.toList f

toMapL :: BiMap k v -> Map.Map k v
toMapL (BiMap f _) = f

toMapR :: BiMap k v -> Map.Map v k
toMapR (BiMap _ b) = b

lookupL :: (Ord k) => k -> BiMap k v -> Maybe v
lookupL k (BiMap f _) = Map.lookup k f

lookupR :: (Ord v) => v -> BiMap k v -> Maybe k
lookupR v (BiMap _ b) = Map.lookup v b

union :: (Ord k, Ord v) => BiMap k v -> BiMap k v -> BiMap k v
union = (<>)

difference :: (Ord k, Ord v) => BiMap k v -> BiMap k v -> BiMap k v
difference (BiMap f1 b1) (BiMap f2 b2) =
let f' = Map.difference f1 f2
b' = Map.difference b1 b2
in BiMap f' b'

insert :: (Ord k, Ord v) => k -> v -> BiMap k v -> BiMap k v
insert k v (BiMap f b) =
BiMap (Map.insert k v f) (Map.insert v k b)

deleteL :: (Ord k, Ord v) => k -> BiMap k v -> BiMap k v
deleteL k bm = case lookupL k bm of
Nothing -> bm
Just v ->
BiMap
(Map.delete k (forward bm))
(Map.delete v (backward bm))

deleteR :: (Ord v, Ord k) => v -> BiMap k v -> BiMap k v
deleteR v bm = flipped $ deleteL v (flipped bm)

keysSetL :: BiMap k v -> Set k
keysSetL (BiMap f _) = Map.keysSet f

keysSetR :: BiMap k v -> Set v
keysSetR (BiMap _ b) = Map.keysSet b

flipped :: BiMap k v -> BiMap v k
flipped (BiMap f b) = BiMap b f
1 change: 1 addition & 0 deletions lib/unison-util-relation/unison-util-relation.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ source-repository head

library
exposed-modules:
Unison.Util.BiMap
Unison.Util.BiMultimap
Unison.Util.Relation
Unison.Util.Relation3
Expand Down
Loading
Loading