Merge pull request #30 from noordstar/patch-iddict

Move Iddict to noordstar/elm-iddict
pull/32/head
noordstar 2024-07-02 10:24:54 +02:00 committed by GitHub
commit a95fbbb856
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 31 additions and 494 deletions

View File

@ -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"

View File

@ -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 =

View File

@ -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, <Iddict with value "hello"> )
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

View File

@ -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

View File

@ -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
}

View File

@ -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)
)
]