matzko / elm-opaque-dict / OpaqueDict

A dictionary mapping unique keys to values. The keys can be any type that can be transformed to a String.

Dictionaries


type alias OpaqueDict key b =
{ keyToString : key -> String
, dict : Dict String ( key
, b ) 
}

A dictionary of keys and values. So a OpaqueDict UserId User is a dictionary that lets you look up a UserId (such as a user Id) and find the associated User. import OpaqueDict exposing (OpaqueDict)

users : OpaqueDict UserId User
users =
    OpaqueDict.fromList userIdToString
        [ ( UserId 1, User (UserId 1) "Alice" 28 1.65 )
        , ( UserId 2, User (UserId 2) "Bob" 19 1.82 )
        , ( UserId 3, User (UserId 3) "Chuck" 33 1.75 )
        ]

type UserId
    = UserId Int

userIdToString : UserId -> String
userIdToString (UserId id) =
    id |> String.fromInt

type alias User =
    { id : UserId
    , name : String
    , age : Int
    , height : Float
    }

Build

empty : (key -> String) -> OpaqueDict key val

Create an empty opaque dictionary providing a function that transforms a key type to a string.

type UserId
    = UserId Int

userIdToString : UserId -> String
userIdToString (UserId id) =
    id |> String.fromInt

-- OpaqueDict.empty userIdToString == [] OpaqueDict.fromList userIdToString

singleton : (key -> String) -> key -> val -> OpaqueDict key val

Create a dictionary with one key-value pair.

singleton userIdToString (UserId 1) "Bob"
-- == ([ ( UserId 1, "Bob" ) ]
-- |> fromList userIdToString
-- )

insert : a -> b -> OpaqueDict a b -> OpaqueDict a b

Insert a key-value pair into a dictionary. Replaces value when there is a collision.

update : a -> (Maybe b -> Maybe b) -> OpaqueDict a b -> OpaqueDict a b

Update the value of a dictionary for a specific key with a given function.

remove : key -> OpaqueDict key val -> OpaqueDict key val

Remove a key-value pair from a dictionary. If the key is not found, no changes are made.

Query

isEmpty : OpaqueDict key val -> Basics.Bool

Determine whether the opaque dictionary is empty.

member : key -> OpaqueDict key val -> Basics.Bool

Determine if a key is in a dictionary.

get : key -> OpaqueDict key val -> Maybe val

Get the value associated with a key. If the key is not found, return Nothing. This is useful when you are not sure if a key will be in the dictionary.

animals = fromList animalFamilyToString [ (Feline, Cat), (Rodent, Mouse) ]
get Feline animals == Just Cat
get Rodent animals == Just Mouse
get Canine animals == Nothing

size : OpaqueDict key val -> Basics.Int

Determine the number of key-value pairs in the dictionary.

Lists

keys : OpaqueDict a b -> List a

Get all of the keys in a dictionary, sorted from lowest to highest by their string form.

values : OpaqueDict key val -> List val

Get the dictionary values.

toList : OpaqueDict key val -> List ( key, val )

Convert a dictionary into an association list of key-value pairs, sorted by keys.

fromList : (key -> String) -> List ( key, val ) -> OpaqueDict key val

Make the opaque dictionary from a list.

Transform

map : (key -> a -> b) -> OpaqueDict key a -> OpaqueDict key b

Apply a function to all values in a dictionary.

foldl : (k -> v -> b -> b) -> b -> OpaqueDict k v -> b

Fold over the key-value pairs in a dictionary from lowest key to highest key.

getAges : OpaqueDict UserId User -> List Int
getAges users =
    OpaqueDict.foldl addAge [] users

addAge : UserId -> User -> List UserId -> List Int
addAge _ user ages =
    user.age :: ages

-- getAges users == [33,19,28]

foldr : (k -> v -> b -> b) -> b -> OpaqueDict k v -> b

Fold over the key-value pairs in a dictionary from highest key to lowest key.

getAges : OpaqueDict UserId User -> List Int
getAges users =
    OpaqueDict.foldr addAge [] users

addAge : UserId -> User -> List UserId -> List Int
addAge _ user ages =
    user.age :: ages

-- getAges users == [28,19,33]

filter : (a -> b -> Basics.Bool) -> OpaqueDict a b -> OpaqueDict a b

Keep only the key-value pairs that pass the given test.

partition : (key -> val -> Basics.Bool) -> OpaqueDict key val -> ( OpaqueDict key val, OpaqueDict key val )

Partition a dictionary according to some test. The first dictionary contains all key-value pairs which passed the test, and the second contains the pairs that did not.

union : OpaqueDict a b -> OpaqueDict a b -> OpaqueDict a b

Combine two dictionaries. If there is a collision, preference is given to the first dictionary.

intersect : OpaqueDict a b -> OpaqueDict a b -> OpaqueDict a b

Keep a key-value pair when its key appears in the second dictionary. Preference is given to values in the first dictionary.

diff : OpaqueDict a b -> OpaqueDict a b -> OpaqueDict a b

Keep a key-value pair when its key does not appear in the second dictionary.

merge : (k -> a -> result -> result) -> (k -> a -> b -> result -> result) -> (k -> b -> result -> result) -> OpaqueDict k a -> OpaqueDict k b -> result -> result

The most general way of combining two dictionaries. You provide three accumulators for when a given key appears:

  1. Only in the left dictionary.
  2. In both dictionaries.
  3. Only in the right dictionary. You then traverse all the keys from lowest to highest, building up whatever you want.

Miscellaneous

decode : (String -> Maybe key) -> (key -> String) -> Json.Decode.Decoder b -> Json.Decode.Decoder (OpaqueDict key b)

Decode a JSON object to an OpaqueDict, analogous Json.Decode.dict.

decodeString (decode animalGroupFromString animalGroupToString int) "{ \"feline\": 3, \"canine\": 5 }"
    == Ok (OpaqueDict.fromList animalGroupToString [ ( Feline, 3 ), ( Canine, 5 ) ])

If you need the keys (like "feline" and "canine") available in the OpaqueDict values as well, you can use a (private) intermediate data structure like Info in this example:

import OpaqueDict exposing (OpaqueDict)
import Json.Decode exposing (..)

type AnimalGroup
    = Feline
    | Canine
    | Rodent

{-| Get the string form of an animal group.
-}
animalGroupToString : AnimalGroup -> String
animalGroupToString group =
    case group of
        Feline ->
            "feline"

        Canine ->
            "canine"

        Rodent ->
            "rodent"

{-| Maybe get an animal group from a string.
-}
animalGroupFromString : String -> Maybe AnimalGroup
animalGroupFromString groupName =
    case groupName of
        "feline" ->
            Just Feline

        "canine" ->
            Just Canine

        "rodent" ->
            Just Rodent

        _ ->
            Nothing

decoder : Decoder (OpaqueDict AnimalGroup Int)
decoder =
    Json.Decode.map (OpaqueDict.map infoToUser) (OpaqueDict.decode Json.Decode.int)


decodeString (decode animalGroupFromString animalGroupToString int) "{ \"feline\": 3, \"canine\": 5 }"
    == Ok (OpaqueDict.fromList animalGroupToString [ ( Feline, 3 ), ( Canine, 5 ) ])

encode : (k -> String) -> (v -> Json.Encode.Value) -> OpaqueDict k v -> Json.Encode.Value

Turn an OpaqueDict into a JSON object. Analogous to Json.Encode.dict.

animalCounts : OpaqueDict AnimalGroup Int
animalCounts =
    [ ( Canine, 5 )
    , ( Feline, 3 )
    ]
        |> fromList animalGroupToString

Encode.encode 0 (OpaqueDict.encode animalGroupToString Encode.int animalCounts)
    == "{\"canine\":5,\"feline\":3}"

majorVersionCompatible : { major1 : Basics.Bool, major2 : Basics.Bool }

Whether this package is compatible with given major version of the package. Really meant as a way to indicate—and force the Elm package system to recognize—a major, breaking change even when there is no change to the API.