lue-bird/elm-keysset/1.0.0

NOTE: the latest version is 5.0.2

KeysSet

type alias KeysSet element =
Typed
Checked
KeysSetTag
Internal
(ElementsWithUniquenessPromises element)

Unsorted data structure that lets you specify aspects that are checked to be unique across all elements.

countries =
    KeysSet.promising
        [ unique .flag, unique .code ]
        |> KeysSet.insertAll
            [ { flag = "🇦🇺", code = "AU", name = "Australia" }
            , { flag = "🇦🇶", code = "AQ", name = "Antarctica" }
            , { flag = "🇱🇧", code = "LB", name = "Lebanon" }
            ]

If you have a key and the aspect to check if it matches, you can find the matching element:

KeysSet.at .flag "🇦🇶" countries
--> Just { flag = "🇦🇶", code = "AQ", name = "Antarctica" }


KeysSet.at .code "LB" countries
--> Just { flag = "🇱🇧", code = "LB", name = "Lebanon" }

create

promising : List (Uniqueness element) -> KeysSet element

A KeysSet with no elements inside, promising that given aspects are unique across all elements. See Uniqueness

KeysSet.promising [ unique .email ]
    |> KeysSet.insert
        { username = "ben", email = "[email protected]" }
    |> KeysSet.insert
        { username = "mai", email = "[email protected]" }
    --> is not inserted.
    --> There's already an element where .email is "[email protected]"

Elements that are inserted must not contain functions, json or regexes. Elm will crash trying to see if they are equal.

uniqueness

type alias Uniqueness element =
element -> element -> { areUnique : Bool }

Check 2 elements if they are equal in a specified aspect. See unique

uniqueInCasedLetter =
    [ unique .inAlphabet
    , unique .lowercase
    , unique .uppercase
    ]


KeysSet.promising uniqueInCasedLetter
    |> KeysSet.insert
        { inAlphabet = 0, lowercase = 'a', uppercase = 'A' }
    |> KeysSet.insert
        { inAlphabet = 0, lowercase = 'b', uppercase = 'B' }
    --> isn't inserted. There's already an element where .inAlphabet is 0.
unique : (element -> aspect_) -> Uniqueness element

Check elements if some aspect is not the same.

unique .name
    { name = "smile", symbol = '😊' }
    { symbol = '🙂', name = "smile" }
--> { areUnique = False }


unique .symbol
    { name = "smile", symbol = '😊' }
    { symbol = '🙂', name = "smile" }
--> { areUnique = True }


unique (\person -> ( person.firstName, person.lastName ))
    { lastName = "jimmy", firstName = "petter", ... }
    { lastName = "jimmy", firstName = "greg", ... }
--> { areUnique = True }

in KeysSet

KeysSet.promising [ unique .email ]
    |> KeysSet.insert
        { username = "ben", email = "[email protected]" }
    |> KeysSet.insert
        { username = "mai", email = "[email protected]" }
    --> is not inserted.
    --> There's already an element where .email is "[email protected]"

scan

equal : KeysSet element -> KeysSet element -> Bool

How can you know if each element in aKeysSet can also be found in bKeysSet?

letterCodes =
    KeysSet.insertAll
        [ { letter = 'a', code = 97 }
        , { letter = 'b', code = 98 }
        ]
        (KeysSet.promising
            [ unique .letter, unique .code ]
        )


fancyCompetingLetterCodes =
    KeysSet.promising
        [ unique .code, unique .letter ]
        |> KeysSet.insert { code = 98, letter = 'b' }
        |> KeysSet.insert { code = 97, letter = 'a' }


letterCodes == fancyCompetingLetterCodes
--> elm crashes

Because a KeysSet's Uniqueness is defined as functions.

(letterCodes |> KeysSet.toList)
== (fancyCompetingLetterCodes |> KeysSet.toList)
--> False

Even though both contain the same elements but in a different order.

→ Don't use == to compare KeysSets

The keys can be non-comparable. There is no obvious order. → You shouldn't rely on order when using functions like fold or toList.

Instead, use

KeysSet.equal
    letterCodes
    fancyCompetingLetterCodes
--> True
isEmpty : KeysSet element_ -> Bool

Whether there are no elements inside.

KeysSet.promising [ unique .name ]
    |> KeysSet.isEmpty
--> True


KeysSet.promising [ unique .name ]
    |> KeysSet.insertAll []
    |> KeysSet.isEmpty
--> True


KeysSet.promising [ unique .name ]
    |> KeysSet.insert { name = "pete" }
    |> KeysSet.isEmpty
--> False
at : (element -> aspect) -> aspect -> KeysSet element -> Maybe element

Try to find an element where a given aspect of it matches a given value.

casedLetters =
    KeysSet.promising
        [ unique .lowercase, unique .uppercase ]
        |> KeysSet.insertAll
            [ { lowercase = 'a', uppercase = 'A' }
            , { lowercase = 'b', uppercase = 'B' }
            ]


lowercase char =
    casedLetters
        |> KeysSet.at .uppercase char
        |> Maybe.map .lowercase


uppercase char =
    casedLetters
        |> KeysSet.at .lowercase char
        |> Maybe.map .uppercase

If the given aspect isn't promised to be unique, at will find the most recently inserted element where the given aspect matches the given value.

ratedOperators =
    KeysSet.promising
        [ unique .symbol, unique .name ]
        |> KeysSet.insertAll
            [ { rating = 0.5, symbol = "<", name = "lt" }
            , { rating = 0.5, symbol = ">", name = "gt" }
            ]


KeysSet.at .rating 0.5 ratedOperators
--> { rating = 0.5, symbol = ">", name = "gt" }
-->     |> Just
size : KeysSet element_ -> Int

How many elements there are.

KeysSet.promising [ unique identity ]
    |> KeysSet.insertAll (List.range 0 41)
    |> KeysSet.size
--> 42
isUnique : element -> KeysSet element -> Bool

Whether this element is considered unique / would it be inserted.

letters =
    KeysSet.promising
        [ unique .lowercase, unique .uppercase ]
        |> KeysSet.insertAll
            [ { lowercase = 'a', uppercase = 'A' }
            , { lowercase = 'b', uppercase = 'B' }
            ]


letters
    |> KeysSet.isUnique
        { lowercase = 'b', uppercase = 'C' }
--> False
-- .lowercase 'b' already exists


letters
    |> KeysSet.isUnique
        { lowercase = 'c', uppercase = 'A' }
--> False
-- .uppercase 'A' already exists


letters
    |> KeysSet.isUnique
        { lowercase = 'c', uppercase = 'C' }
--> True
all : (element -> Bool) -> KeysSet element -> Bool

Whether all elements pass a test.

KeysSet.promising
    [ unique .username, unique .email ]
    |> KeysSet.insertAll
        [ { username = "fred", priority = 1, email = "[email protected]" }
        , { username = "gria", priority = 3, email = "[email protected]" }
        ]
    |> KeysSet.all (\user -> user.priority < 4)
--> True
any : (element -> Bool) -> KeysSet element -> Bool

Whether there is least one element that passes a test.

KeysSet.promising
    [ unique .username, unique .email ]
    |> KeysSet.insertAll
        [ { username = "fred", priority = 1, email = "[email protected]" }
        , { username = "gria", priority = 3, email = "[email protected]" }
        ]
    |> KeysSet.any (\user -> user.priority > 4)
--> False


member needle =
    KeysSet.any ((==) needle)

modify

insert : element -> KeysSet element -> KeysSet element

Put an element into the KeysSet.

If there is already an element where some aspect that is promised to be unique is equal, (see Uniqueness), the KeysSet remains unchanged.

KeysSet.promising
    [ unique .lowercase, unique .uppercase ]
    |> KeysSet.insert
        { lowercase = 'b', uppercase = 'B', rating = 0.5 }
        --> is inserted
    |> KeysSet.insert
        { lowercase = 'a', uppercase = 'A', rating = 0.5 }
        --> is inserted
        -- .rating is not specified as unique
    |> KeysSet.insert
        { lowercase = 'b', uppercase = 'C', rating = 0 }
        --> is ignored
        -- .lowercase 'b' already exists
    |> KeysSet.insert
        { lowercase = 'c', uppercase = 'A', rating = 0 }
        --> is ignored
        -- .uppercase 'A' already exists
    |> KeysSet.insert
        { lowercase = 'c', uppercase = 'C', rating = 0.6 }
        --> is inserted
insertAll : List element -> KeysSet element -> KeysSet element

Conveniently insert a list of elements. See insert.

KeysSet.promising
    [ unique .open, unique .closed ]
    |> KeysSet.insertAll
        [ { open = '(', closed = ')' }
        , { open = '{', closed = '}' }
        ]
--> KeysSet.promising [ unique .open, unique .closed ]
-->     |> KeysSet.insert { open = '(', closed = ')' }
-->     |> KeysSet.insert { open = '{', closed = '}' }
remove : (element -> aspect) -> aspect -> KeysSet element -> KeysSet element

Remove the element where a given aspect of the element matches a given value.

openClosedBrackets =
    KeysSet.promising
        [ unique .open, unique .closed ]
        |> KeysSet.insert
            { open = "(", closed = ")" }


openClosedBrackets
    |> KeysSet.remove .open ")"
--> no change
-- .open is never ")"


openClosedBrackets
    |> KeysSet.remove .closed ")"
--> removes { open = "(", closed = ")" }

If there the checked aspect isn't promised to be unique, remove acts as a filter.

KeysSet.promising
    [ unique .open, unique .closed ]
    |> KeysSet.insertAll
        [ { open = "[", closed = "]", meaning = List }
        , { open = "<", closed = ">", meaning = Custom }
        , { open = "\\", closed = "/", meaning = Custom }
        ]
    |> KeysSet.remove .meaning Custom
--> only { open = "[", closed = "]", meaning = List } remains

If filtering is your intention, use KeysSet.when.

update : (a -> b) -> b -> (a -> a) -> KeysSet a -> KeysSet a

Change the element with the matching aspect based on its current value.

KeysSet.promising
    [ unique .username, unique .email ]
    |> KeysSet.insertAll
        [ { username = "fred", priority = 1, email = "[email protected]" }
        , { username = "gria", priority = 3, email = "[email protected]" }
        ]
    |> KeysSet.update .username
        "fred"
        (\user -> { user | priority = p.priority + 3 })

If this aspect isn't unique, all elements with the matching aspect are updated.

KeysSet.promising [ unique .email ]
    |> KeysSet.insertAll
        [ { username = "fred", priority = 1, email = "[email protected]" }
        , { username = "fred", priority = 3, email = "[email protected]" }
        ]
    |> KeysSet.update .username
        "fred"
        (\user -> { user | priority = p.priority + 3 })

Every fred gets a higher priority!

updateAll : (element -> element) -> KeysSet element -> KeysSet element

Change every element based on its current value.

rankUpAllUsers =
    KeysSet.updateAll
        (\user -> { user | rank = user.rank + 1 })

If aspects that are promised to be unique become the same for 2 elements, the more recently inserted element is chosen.

Use map if your function changes the type of the element.

filter

when : (element -> Bool) -> KeysSet element -> KeysSet element

Only keep elements that satisfy a test.

operators =
    KeysSet.promising
        [ unique .symbol, unique .name ]
        |> KeysSet.insertAll
            [ { symbol = ">", name = "gt" }
            , { symbol = "<", name = "lt" }
            , { symbol = "==", name = "eq" }
            ]


singleCharOperators =
    operators
        |> KeysSet.when
            (.symbol >> String.length >> (==) 1)


--> KeysSet.promising
-->     [ unique .symbol, unique .name ]
-->     |> KeysSet.insertAll
-->         [ { symbol = ">", name = "gt" }
-->         , { symbol = "<", name = "lt" }
-->         ]

transform

fold : (element -> acc -> acc) -> acc -> KeysSet element -> acc

Reduce the elements from most recently to least recently inserted element.

The keys can be non-comparable. There is no obvious order.

→ You shouldn't rely on order when using functions like fold or toList.

brackets =
    KeysSet.promising
        [ unique .open, unique .closed ]
        |> KeysSet.insertAll
            [ { open = '(', closed = ')' }
            , { open = '{', closed = '}' }
            ]


openingAndClosing =
    brackets
        |> KeysSet.fold
            (\{ open, closed } ->
                (::) (String.fromList [ open, closed ])
            )
            []


--> []
-->     |> (::) (String.fromList [ '{' '}' ])
-->     |> (::) (String.fromList [ '(' ')' ])
toList : KeysSet element -> List element

The List containing all elements from most recently (= head) to least recently inserted element.

The keys can be non-comparable. There is no obvious order.

→ You shouldn't rely on order when using functions like fold or toList.

mostRecentlyInserted =
    List.head << KeysSet.toList


KeysSet.promising
    [ unique .open, unique .closed ]
    |> KeysSet.insertAll
        [ { open = '(', closed = ')' }
        , { open = '{', closed = '}' }
        ]
    |> KeysSet.toList
--> [ { open = '{', closed = '}' }
--> , { open = '(', closed = ')' }
--> ]
map :
(element -> mappedElement)
-> List (Uniqueness mappedElement)
-> KeysSet element
-> KeysSet mappedElement

Alter every element based on its current value.

digitNames =
    KeysSet.promising
        [ unique .number, unique .name ]
        |> KeysSet.insertAll
            [ { number = 0, name = "zero" }
            , { number = 1, name = "one" }
            ]


mathSymbolNames =
    digitNames
        |> KeysSet.map
            (\{ number, name } ->
                { symbol = String.fromInt number, name = name }
            )
            [ unique .symbol, unique .name ]
        |> KeysSet.insert { symbol = "+", name = "plus" }

If the type doesn't change, use KeysSet.updateAll.