diff --git a/elm.json b/elm.json index aad9344..7c3b668 100644 --- a/elm.json +++ b/elm.json @@ -20,7 +20,8 @@ "elm/time": "1.0.0 <= v < 2.0.0", "elm/url": "1.0.0 <= v < 2.0.0", "micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0", - "miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0" + "miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0", + "noordstar/elm-iddict": "1.0.1 <= v < 2.0.0" }, "test-dependencies": { "elm-explorations/test": "2.1.2 <= v < 3.0.0" diff --git a/src/Internal/Config/Text.elm b/src/Internal/Config/Text.elm index 806ac16..2bfe125 100644 --- a/src/Internal/Config/Text.elm +++ b/src/Internal/Config/Text.elm @@ -118,7 +118,6 @@ docs : , event : TypeDocs , hashdict : TypeDocs , ibatch : TypeDocs - , iddict : TypeDocs , itoken : TypeDocs , mashdict : TypeDocs , room : TypeDocs @@ -169,12 +168,6 @@ docs = [ "The internal batch tracks a patch of events on the Matrix timeline." ] } - , iddict = - { name = "Iddict" - , description = - [ "An iddict automatically handles creating appropriate keys by incrementally assiging a new key to new values." - ] - } , itoken = { name = "IToken" , description = diff --git a/src/Internal/Tools/Iddict.elm b/src/Internal/Tools/Iddict.elm deleted file mode 100644 index da718f2..0000000 --- a/src/Internal/Tools/Iddict.elm +++ /dev/null @@ -1,198 +0,0 @@ -module Internal.Tools.Iddict exposing - ( Iddict - , empty, singleton, insert, map, remove - , isEmpty, member, get, size - , keys, values - , coder, encode, decoder - ) - -{-| The id-dict is a data type that lets us store values in a dictionary using -unique identifiers. This can be used as a dictionary where the keys do not -matter. - -The benefit of the iddict is that it generates the keys FOR you. This way, you -do not need to generate identifiers yourself. - - -## Id-dict - -@docs Iddict - - -## Build - -@docs empty, singleton, insert, map, remove - - -## Query - -@docs isEmpty, member, get, size - - -## Lists - -@docs keys, values - - -## JSON coders - -@docs coder, encode, decoder - --} - -import FastDict as Dict exposing (Dict) -import Internal.Config.Text as Text -import Internal.Tools.Json as Json - - -{-| The Iddict data type. --} -type Iddict a - = Iddict - { cursor : Int - , dict : Dict Int a - } - - -{-| Define how an Iddict can be encoded and decoded to and from a JSON value. --} -coder : Json.Coder a -> Json.Coder (Iddict a) -coder x = - Json.object2 - { name = Text.docs.iddict.name - , description = Text.docs.iddict.description - , init = - \c d -> - Iddict - { cursor = - Dict.keys d - |> List.maximum - |> Maybe.map ((+) 1) - |> Maybe.withDefault 0 - |> max (Dict.size d) - |> max c - , dict = d - } - } - (Json.field.optional.withDefault - { fieldName = "cursor" - , toField = \(Iddict i) -> i.cursor - , description = Text.fields.iddict.cursor - , coder = Json.int - , default = ( 0, [] ) - , defaultToString = String.fromInt - } - ) - (Json.field.required - { fieldName = "dict" - , toField = \(Iddict i) -> i.dict - , description = Text.fields.iddict.dict - , coder = Json.fastIntDict x - } - ) - - -{-| Decode an id-dict from a JSON value. --} -decoder : Json.Coder a -> Json.Decoder (Iddict a) -decoder x = - Json.decode (coder x) - - -{-| Create an empty id-dict. --} -empty : Iddict a -empty = - Iddict - { cursor = 0 - , dict = Dict.empty - } - - -{-| Encode an id-dict to a JSON value. --} -encode : Json.Coder a -> Json.Encoder (Iddict a) -encode x = - Json.encode (coder x) - - -{-| Get a value from the id-dict using its key. --} -get : Int -> Iddict a -> Maybe a -get k (Iddict { dict }) = - Dict.get k dict - - -{-| Insert a new value into the id-dict. Given that the id-dict generates its -key, the function returns both the updated id-dict as the newly generated key. - - x = empty |> insert "hello" -- ( 0, ) - - case x of - ( _, iddict ) -> - get 0 iddict -- Just "hello" - --} -insert : a -> Iddict a -> ( Int, Iddict a ) -insert v (Iddict d) = - ( d.cursor - , Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict } - ) - - -{-| Determine if an id-dict is empty. --} -isEmpty : Iddict a -> Bool -isEmpty (Iddict d) = - Dict.isEmpty d.dict - - -{-| Get all of the keys from the id-dict, sorted from lowest to highest. --} -keys : Iddict a -> List Int -keys (Iddict { dict }) = - Dict.keys dict - - -{-| Map an existing value at a given key, if it exists. If it does not exist, -the operation does nothing. --} -map : Int -> (a -> a) -> Iddict a -> Iddict a -map k f (Iddict d) = - Iddict { d | dict = Dict.update k (Maybe.map f) d.dict } - - -{-| Determine if a key is in an id-dict. --} -member : Int -> Iddict a -> Bool -member k (Iddict d) = - k < d.cursor && Dict.member k d.dict - - -{-| Remove a key-value pair from the id-dict. If the key is not found, no -changes are made. --} -remove : Int -> Iddict a -> Iddict a -remove k (Iddict d) = - Iddict { d | dict = Dict.remove k d.dict } - - -{-| Create an id-dict with a single value. --} -singleton : a -> ( Int, Iddict a ) -singleton v = - insert v empty - - -{-| Determine the number of key-value pairs in the id-dict. --} -size : Iddict a -> Int -size (Iddict d) = - Dict.size d.dict - - -{-| Get all of the values from an id-dict, in the order of their keys. --} -values : Iddict a -> List a -values (Iddict { dict }) = - Dict.values dict diff --git a/src/Internal/Tools/Json.elm b/src/Internal/Tools/Json.elm index 28ccd8c..1a3116f 100644 --- a/src/Internal/Tools/Json.elm +++ b/src/Internal/Tools/Json.elm @@ -3,7 +3,7 @@ module Internal.Tools.Json exposing , Encoder, encode, Decoder, decode, Value , succeed, fail, andThen, lazy, map , Docs(..), RequiredField(..), toDocs - , list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe + , list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe , Field, field, parser , object2, object3, object4, object5, object6, object7, object8, object9, object10, object11 ) @@ -49,7 +49,7 @@ module to build its encoders and decoders. ## Data types -@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe +@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe ## Objects @@ -68,6 +68,7 @@ Once all fields are constructed, the user can create JSON objects. import Dict as SlowDict import FastDict +import Iddict exposing (Iddict) import Internal.Config.Log as Log exposing (Log) import Internal.Config.Text as Text import Internal.Tools.DecodeExtra as D @@ -141,6 +142,7 @@ type Docs = DocsBool | DocsDict Docs | DocsFloat + | DocsIddict Docs | DocsInt | DocsIntDict Docs | DocsLazy (() -> Docs) @@ -467,6 +469,25 @@ float = } +{-| Define an Iddict as defined in +[noordstar/elm-iddict](https://package.elm-lang.org/packages/noordstar/elm-iddict/latest/). +-} +iddict : Coder a -> Coder (Iddict a) +iddict (Coder old) = + Coder + { encoder = Iddict.encode old.encoder + , decoder = + D.andThen + (\( out, logs ) -> + D.succeed out + |> Iddict.decoder + |> D.map (\o -> ( o, logs )) + ) + old.decoder + , docs = DocsIddict old.docs + } + + {-| Define an int value. -} int : Coder Int diff --git a/src/Internal/Values/Timeline.elm b/src/Internal/Values/Timeline.elm index 9bded76..ea038a1 100644 --- a/src/Internal/Values/Timeline.elm +++ b/src/Internal/Values/Timeline.elm @@ -67,10 +67,10 @@ events! -} import FastDict as Dict exposing (Dict) +import Iddict exposing (Iddict) import Internal.Config.Text as Text import Internal.Filter.Timeline as Filter exposing (Filter) import Internal.Tools.Hashdict as Hashdict exposing (Hashdict) -import Internal.Tools.Iddict as Iddict exposing (Iddict) import Internal.Tools.Json as Json import Recursion import Recursion.Traverse @@ -210,7 +210,7 @@ coder = { fieldName = "batches" , toField = \(Timeline t) -> t.batches , description = Text.fields.timeline.batches - , coder = Iddict.coder coderIBatch + , coder = Json.iddict coderIBatch } ) (Json.field.required @@ -411,8 +411,8 @@ connectIBatchToIToken (IBatchPTR bptr) pointer (Timeline tl) = Timeline { tl | batches = - Iddict.map bptr - (\batch -> { batch | end = pointer }) + Iddict.update bptr + (Maybe.map (\batch -> { batch | end = pointer })) tl.batches , tokens = Hashdict.map tptr @@ -437,8 +437,8 @@ connectITokenToIBatch pointer (IBatchPTR bptr) (Timeline tl) = (\token -> { token | starts = Set.insert bptr token.starts }) tl.tokens , batches = - Iddict.map bptr - (\batch -> { batch | start = pointer }) + Iddict.update bptr + (Maybe.map (\batch -> { batch | start = pointer })) tl.batches } diff --git a/tests/Test/Tools/Iddict.elm b/tests/Test/Tools/Iddict.elm deleted file mode 100644 index 708ecb7..0000000 --- a/tests/Test/Tools/Iddict.elm +++ /dev/null @@ -1,280 +0,0 @@ -module Test.Tools.Iddict exposing (..) - -import Expect -import Fuzz exposing (Fuzzer) -import Internal.Tools.Iddict as Iddict exposing (Iddict) -import Internal.Tools.Json as Json -import Json.Decode as D -import Json.Encode as E -import Test exposing (..) - - -fuzzer : Fuzzer a -> Fuzzer (Iddict a) -fuzzer fuz = - fuz - |> Fuzz.pair Fuzz.bool - |> Fuzz.list - |> Fuzz.map - (\items -> - List.foldl - (\( rm, item ) dict -> - case Iddict.insert item dict of - ( key, d ) -> - if rm then - Iddict.remove key d - - else - d - ) - Iddict.empty - items - ) - - -empty : Test -empty = - describe "empty" - [ test "isEmpty" - (Iddict.empty - |> Iddict.isEmpty - |> Expect.equal True - |> always - ) - , fuzz Fuzz.int - "No members" - (\i -> - Iddict.empty - |> Iddict.member i - |> Expect.equal False - ) - , fuzz Fuzz.int - "Get gets Nothing" - (\i -> - Iddict.empty - |> Iddict.get i - |> Expect.equal Nothing - ) - , test "Size = 0" - (Iddict.empty - |> Iddict.size - |> Expect.equal 0 - |> always - ) - , test "No keys" - (Iddict.empty - |> Iddict.keys - |> Expect.equal [] - |> always - ) - , test "No values" - (Iddict.empty - |> Iddict.values - |> Expect.equal [] - |> always - ) - , test "JSON encode -> decode -> empty" - (Iddict.empty - |> Iddict.encode Json.value - |> D.decodeValue (Iddict.decoder Json.value) - |> Result.map Tuple.first - |> Expect.equal (Ok Iddict.empty) - |> always - ) - , test "JSON encode" - (Iddict.empty - |> Iddict.encode Json.value - |> E.encode 0 - |> Expect.equal "{\"dict\":{}}" - |> always - ) - , test "JSON decode" - ("{\"dict\":{}}" - |> D.decodeString (Iddict.decoder Json.value) - |> Result.map Tuple.first - |> Expect.equal (Ok Iddict.empty) - |> always - ) - ] - - -singleton : Test -singleton = - let - singleFuzzer : Fuzzer (Iddict Int) - singleFuzzer = - Fuzz.map - (\i -> - Iddict.singleton i - |> Tuple.second - ) - Fuzz.int - in - describe "singleton" - [ fuzz singleFuzzer - "not isEmpty" - (\single -> - single - |> Iddict.isEmpty - |> Expect.equal False - ) - , fuzz Fuzz.int - "singleton == insert empty" - (\i -> - Iddict.empty - |> Iddict.insert i - |> Expect.equal (Iddict.singleton i) - ) - , fuzz Fuzz.int - "First item is key 0" - (\i -> - Iddict.singleton i - |> Tuple.first - |> Expect.equal 0 - ) - , fuzz singleFuzzer - "Key 0 is member" - (\single -> - single - |> Iddict.member 0 - |> Expect.equal True - ) - , fuzz Fuzz.int - "Key 0 get returns Just value" - (\i -> - Iddict.singleton i - |> Tuple.second - |> Iddict.get 0 - |> Expect.equal (Just i) - ) - , fuzz singleFuzzer - "Size == 1" - (\single -> - single - |> Iddict.size - |> Expect.equal 1 - ) - , fuzz Fuzz.int - "Only key 0" - (\i -> - Iddict.singleton i - |> Tuple.second - |> Iddict.keys - |> Expect.equal [ 0 ] - ) - , fuzz Fuzz.int - "Only value value" - (\i -> - Iddict.singleton i - |> Tuple.second - |> Iddict.values - |> Expect.equal [ i ] - ) - , fuzz singleFuzzer - "JSON encode -> decode -> singleton" - (\single -> - single - |> Iddict.encode Json.int - |> D.decodeValue (Iddict.decoder Json.int) - |> Result.map Tuple.first - |> Expect.equal (Ok single) - ) - , fuzz Fuzz.int - "JSON encode" - (\i -> - Iddict.singleton i - |> Tuple.second - |> Iddict.encode Json.int - |> E.encode 0 - |> Expect.equal ("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}") - ) - , fuzz Fuzz.int - "JSON decode" - (\i -> - ("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}") - |> D.decodeString (Iddict.decoder Json.int) - |> Result.map Tuple.first - |> Tuple.pair 0 - |> Expect.equal (Iddict.singleton i |> Tuple.mapSecond Ok) - ) - ] - - -insert : Test -insert = - describe "insert" - [ fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "Add something" - (\d i -> - case Iddict.insert i d of - ( key, dict ) -> - dict - |> Iddict.get key - |> Expect.equal (Just i) - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "Never isEmpty" - (\d i -> - Iddict.insert i d - |> Tuple.second - |> Iddict.isEmpty - |> Expect.equal False - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "New key" - (\d i -> - case Iddict.insert i d of - ( key, dict ) -> - dict - |> Iddict.remove key - |> Iddict.insert i - |> (\( newKey, _ ) -> - Expect.notEqual key newKey - ) - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "New dict" - (\d i -> - case Iddict.insert i d of - ( key, dict ) -> - dict - |> Iddict.remove key - |> Iddict.insert i - |> (\( _, newDict ) -> - Expect.notEqual dict newDict - ) - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "Inserted value is member" - (\d i -> - case Iddict.insert i d of - ( key, dict ) -> - dict - |> Iddict.member key - |> Expect.equal True - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "Get inserted value" - (\d i -> - case Iddict.insert i d of - ( key, dict ) -> - dict - |> Iddict.get key - |> Expect.equal (Just i) - ) - , fuzz2 (fuzzer Fuzz.int) - Fuzz.int - "size = size + 1" - (\d i -> - case Iddict.insert i d of - ( _, dict ) -> - Expect.equal - (Iddict.size dict) - (Iddict.size d + 1) - ) - ]