Skip to content

Instantly share code, notes, and snippets.

@i-am-the-slime
Last active February 3, 2024 17:12
Show Gist options
  • Save i-am-the-slime/652b31f3c66c5e2bc06e05cce0270ab6 to your computer and use it in GitHub Desktop.
Save i-am-the-slime/652b31f3c66c5e2bc06e05cce0270ab6 to your computer and use it in GitHub Desktop.
Object that allows using any String newtype as the keys
module Sentence.Biz.Types.ObjectMap
( ObjectMap(..)
, empty
, isEmpty
, size
, singleton
, insert
, lookup
, toUnfoldable
, toAscUnfoldable
, fromFoldable
, fromFoldableWith
, fromFoldableWithIndex
, fromHomogeneous
, delete
, pop
, member
, alter
, update
, mapWithKey
, filterWithKey
, filterKeys
, filter
, keys
, values
, union
, unionWith
, unions
, isSubmap
, fold
, foldMap
, foldM
, foldMaybe
, all
-- , thawST
-- , freezeST
-- , runST
, toArrayWithKey
) where
import Prelude
import Data.Array (sortWith, toUnfoldable) as A
import Data.Either (Either(..))
import Data.Foldable (class Foldable, foldl, foldr)
import Data.FoldableWithIndex (class FoldableWithIndex, forWithIndex_)
import Data.FunctorWithIndex (class FunctorWithIndex)
import Data.Lens.AffineTraversal (affineTraversal)
import Data.Lens.At (class At)
import Data.Lens.Index (class Index)
import Data.Lens.Lens (lens)
import Data.Maybe (Maybe(Just), maybe, maybe')
import Data.Newtype (class Newtype, unwrap)
import Data.Traversable (class Traversable)
import Data.TraversableWithIndex (class TraversableWithIndex)
import Data.Tuple (Tuple(Tuple), fst, uncurry)
import Data.Unfoldable (class Unfoldable)
import Foreign.Object (Object, runST)
import Foreign.Object (toAscUnfoldable) as A
import Foreign.Object as Object
import Foreign.Object.ST (new, poke) as OST
import Pinboard.Shape.Types (ShapeData)
import Prim.Coerce (class Coercible)
import Safe.Coerce (coerce)
import TLDraw.Types (PSShape)
import Type.Row.Homogeneous (class Homogeneous)
import Unsafe.Coerce (unsafeCoerce)
import Web.DOM.Element (id)
import Yoga.JSON (class ReadForeign, class WriteForeign, writeImpl)
import Yoga.JSON (readImpl, writeImpl) as JSON
newtype ObjectMap :: Type -> Type -> Type
newtype ObjectMap k a = ObjectMap (Object a)
derive instance Newtype (ObjectMap key a) _
derive newtype instance (Eq key, Eq a) => Eq (ObjectMap key a)
derive newtype instance (Ord key, Ord a) => Ord (ObjectMap key a)
derive newtype instance (Show key, Show a) => Show (ObjectMap key a)
derive newtype instance Functor (ObjectMap key)
instance Newtype key String => FunctorWithIndex key (ObjectMap key) where
mapWithIndex = unsafeCoerce Object.mapWithKey
derive newtype instance Foldable (ObjectMap key)
derive newtype instance Semigroup value => Semigroup (ObjectMap key value)
derive newtype instance Monoid value => Monoid (ObjectMap key value)
instance Newtype key String => FoldableWithIndex key (ObjectMap key) where
foldlWithIndex f = fold (flip f)
foldrWithIndex f z m = foldr (uncurry f) z (toArrayWithKey Tuple m)
foldMapWithIndex = foldMap
derive newtype instance Newtype key String => Traversable (ObjectMap key)
fold :: forall key a z. Newtype key String => (z -> key -> a -> z) -> z -> ObjectMap key a -> z
fold = unsafeCoerce Object.fold
values :: forall key a. Newtype key String => ObjectMap key a -> Array a
values = Object.values <<< coerce
toArrayWithKey :: forall key a b. Newtype key String => (key -> a -> b) -> ObjectMap key a -> Array b
toArrayWithKey = unsafeCoerce Object.toArrayWithKey
-- | Create an empty `ObjectMap key a`
empty :: forall key a. Newtype key String => ObjectMap key a
empty = unsafeCoerce Object.empty
-- | Create an `ObjectMap key a` with one key/value pair
singleton :: forall key a. Newtype key String => key -> a -> ObjectMap key a
singleton = unsafeCoerce Object.singleton
-- | Lookup the value for a key in a map
lookup :: forall key a. Newtype key String => key -> ObjectMap key a -> Maybe a
lookup = unsafeCoerce Object.lookup
-- | Test whether a `key` appears as a key in a map
member :: forall key a. Newtype key String => key -> ObjectMap key a -> Boolean
member = unsafeCoerce Object.member
-- | Insert or replace a key/value pair in a map
insert :: forall key a. Newtype key String => key -> a -> ObjectMap key a -> ObjectMap key a
insert = unsafeCoerce Object.insert
-- | Delete a key and value from a map
delete :: forall key a. Newtype key String => key -> ObjectMap key a -> ObjectMap key a
delete = unsafeCoerce Object.delete
-- | Delete a key and value from a map, returning the value
-- | as well as the subsequent map
pop :: forall key a. Newtype key String => key -> ObjectMap key a -> Maybe (Tuple a (ObjectMap key a))
pop = unsafeCoerce Object.pop
-- | Insert, remove or update a value for a key in a map
alter :: forall key a. Newtype key String => (Maybe a -> Maybe a) -> key -> ObjectMap key a -> ObjectMap key a
alter = unsafeCoerce Object.alter
-- | Remove or update a value for a key in a map
update :: forall key a. Newtype key String => (a -> Maybe a) -> key -> ObjectMap key a -> ObjectMap key a
update = unsafeCoerce Object.update
-- | Keeps only the key/value pairs satisfying a predicate
filter :: forall key a. Newtype key String => (a -> Boolean) -> ObjectMap key a -> ObjectMap key a
filter = unsafeCoerce Object.filter
-- | Keeps only the key/value pairs satisfying a predicate which also takes a key
filterWithKey :: forall key a. Newtype key String => (key -> a -> Boolean) -> ObjectMap key a -> ObjectMap key a
filterWithKey = unsafeCoerce Object.filterWithKey
filterKeys :: forall key a. Newtype key String => (key -> Boolean) -> ObjectMap key a -> ObjectMap key a
filterKeys = unsafeCoerce Object.filterKeys
-- | Create an `ObjectMap key a` from a foldable collection of key/value pairs
fromFoldable :: forall f key a. Foldable f => Newtype key String => f (Tuple key a) -> ObjectMap key a
fromFoldable tuples = unsafeCoerce (Object.fromFoldable (unsafeCoerce tuples :: f (Tuple String a)))
-- | Create an `Object a` from a `key`-indexed foldable collection
fromFoldableWithIndex :: forall f key a. FoldableWithIndex key f => Newtype key String => f a -> ObjectMap key a
fromFoldableWithIndex l = unsafeCoerce $ runST do
s <- OST.new
forWithIndex_ l \k v -> OST.poke (unwrap k) v s
pure s
fromFoldableWith :: forall f key a. Foldable f => Newtype key String => (a -> a -> a) -> f (Tuple key a) -> ObjectMap key a
fromFoldableWith f tuples = unsafeCoerce (Object.fromFoldableWith f (unsafeCoerce tuples :: f (Tuple String a)))
-- | Create an `ObjectMap a` from a homogeneous record, i.e. all of the record
-- | fields are of the same type.
fromHomogeneous :: forall r key a. Newtype key String => Homogeneous r a => { | r } -> ObjectMap key a
fromHomogeneous = unsafeCoerce
-- | Get all keys of a map
keys :: forall key a. Newtype key String => ObjectMap key a -> Array key
keys = unsafeCoerce Object.keys
-- | Compute the union of two maps, preferring the first map in the case of duplicate keys.
union :: forall key a. Newtype key String => ObjectMap key a -> ObjectMap key a -> ObjectMap key a
union = unsafeCoerce Object.union
-- | Compute the union of two maps, using the specified function to combine values for duplicate keys.
unionWith :: forall key a. Newtype key String => (a -> a -> a) -> ObjectMap key a -> ObjectMap key a -> ObjectMap key a
unionWith = unsafeCoerce Object.unionWith
-- | Compute the union of a collection of maps
unions :: forall key f a. Newtype key String => Foldable f => f (ObjectMap key a) -> ObjectMap key a
unions = foldl union empty
-- | Returns true if there are no key/value pairs in the map
isEmpty :: forall key a. Newtype key String => ObjectMap key a -> Boolean
isEmpty = unsafeCoerce Object.isEmpty
-- | The number of key/value pairs in a map
size :: forall key a. Newtype key String => ObjectMap key a -> Number
size = unsafeCoerce Object.size
-- | Unfolds a map into a list of key/value pairs
toUnfoldable :: forall key a f. Newtype key String => Unfoldable f => ObjectMap key a -> f (Tuple key a)
toUnfoldable = A.toUnfoldable <<< toArrayWithKey Tuple
-- | Unfolds a map into a list of key/value pairs which is guaranteed to be
-- | sorted by key
toAscUnfoldable :: forall f key a. Ord key => Newtype key String => Unfoldable f => ObjectMap key a -> f (Tuple key a)
toAscUnfoldable = A.toUnfoldable <<< A.sortWith fst <<< toArrayWithKey Tuple
-- | Apply a function of two arguments to each key/value pair, producing a new map
mapWithKey :: forall key a b. Newtype key String => (key -> a -> b) -> ObjectMap key a -> ObjectMap key b
mapWithKey = unsafeCoerce Object.mapWithKey
-- | Test whether one map contains all of the keys and values contained in another map
isSubmap :: forall key a. Newtype key String => Eq a => ObjectMap key a -> ObjectMap key a -> Boolean
isSubmap map1 map2 = Object.isSubmap (unsafeCoerce map1 :: Object a) (unsafeCoerce map2 :: Object a)
-- | Fold the keys and values of an object, accumulating values using some
-- | `Monoid`.
foldMap :: forall key a m. Newtype key String => Monoid m => (key -> a -> m) -> ObjectMap key a -> m
foldMap f = unsafeCoerce (Object.foldMap ((unsafeCoerce f :: String -> a -> m)))
-- | Fold the keys and values of an object, accumulating values and effects in
-- | some `Monad`.
foldM :: forall key a m z. Newtype key String => Monad m => (z -> key -> a -> m z) -> z -> ObjectMap key a -> m z
foldM f z = unsafeCoerce (Object.foldM ((unsafeCoerce f :: z -> String -> a -> m z)) z)
-- | Fold the keys and values of a map.
-- |
-- | This function allows the folding function to terminate the fold early,
-- | using `Maybe`.
foldMaybe :: forall key a z. Newtype key String => (z -> key -> a -> Maybe z) -> z -> ObjectMap key a -> z
foldMaybe f z = unsafeCoerce (Object.foldMaybe ((unsafeCoerce f :: z -> String -> a -> Maybe z)) z)
-- | Test whether all key/value pairs in a `Object` satisfy a predicate.
all :: forall key a. Newtype key String => (key -> a -> Boolean) -> ObjectMap key a -> Boolean
all f = unsafeCoerce (Object.all ((unsafeCoerce f :: String -> a -> Boolean)))
---------------------------------------------
---- Lens stuff
instance Newtype key String => Index (ObjectMap key v) key v where
ix k = affineTraversal set pre
where
set :: ObjectMap key v -> v -> ObjectMap key v
set s b = update (\_ -> Just b) k s
pre :: ObjectMap key v -> Either (ObjectMap key v) v
pre s = maybe (Left s) Right $ lookup k s
instance Newtype key String => At (ObjectMap key v) key v where
at k = lens (lookup k) \m -> maybe' (\_ -> delete k m) \v -> insert k v m
-- Serialisation
instance (Newtype key String, WriteForeign a) => WriteForeign (ObjectMap key a) where
writeImpl obj = JSON.writeImpl (unsafeCoerce obj :: Object a)
instance (Newtype key String, ReadForeign a) => ReadForeign (ObjectMap key a) where
readImpl fgn = unsafeCoerce (JSON.readImpl fgn :: _ (Object a))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment