Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: More flexible IntDict.Safe API #8

Open
anka-213 opened this issue Sep 20, 2019 · 1 comment
Open

Suggestion: More flexible IntDict.Safe API #8

anka-213 opened this issue Sep 20, 2019 · 1 comment

Comments

@anka-213
Copy link

anka-213 commented Sep 20, 2019

With the current safe API we check if the key is valid every time we make an operation and we need to handle the error case for every single operation, even if we have already checked that the key is safe. It also forces us to delay the verification until the very last moment.

My suggestion is to decouple the verification from the usage by adding a new opaque wrapper type SafeKey that can only be constructed by verifying that the key is safe. The new API could look something like this:

module IntDict.Safe.Key exposing (SafeKey, verify, toInt)

-- This type is evidence that the key has been verified
-- (The constructor should not be exposed)
type SafeKey
    = SafeKey Int

{- | Check if a key is valid -}
verify : Int -> Maybe SafeKey
verify key =
    if isValidKey key then
        Just (SafeKey key)
    else
        Nothing

toInt : SafeKey -> Int
toInt (SafeKey key) =
    key
module IntDict.Safe exposing (..)

import IntDict
import IntDict.Safe.Key as Key exposing (SafeKey)

insert : SafeKey -> v -> IntDict v -> IntDict v
insert k v dict =
    IntDict.insert (Key.toInt k) v dict

-- etc...

You would use it like this:

import IntDict exposing (IntDict)
import IntDict.Safe as Safe
import IntDict.Safe.Key as Key


insertSomeStuff : Int -> IntDict String -> IntMap String
insertSomeStuff key dict =
    case Key.verify key of
        Nothing ->
            Debug.todo "Handle errors"

        Just safeKey ->
            Safe.insert safeKey "SomeStuff" dict

Contrast this with the old version

import IntDict exposing (IntDict)
import IntDict.Safe exposing (InvalidKey(..), insert)


insertSomeStuff : Int -> IntDict String -> IntMap String
insertSomeStuff key dict =
    case Safe.insert safeKey "SomeStuff" dict of
        Err InvalidKey ->
            Debug.todo "Handle errors"

        Ok newDict ->
            newDict

We haven't really lost anything in terms of verbosity, but we have won in flexibility and separation of concerns, since we can now verify the keys at the edges of the program, then store and use the verified keys everywhere else and only handle errors once if we want to.

I am aware that this is a very much breaking change, so I should probably put this in a separate library instead, at least initially. However, if you think it is worthwhile to change the API here, I can write up a pull request for use in the next major version.


[note 1]: This pattern can be seen as an extension of the make impossible states impossible idea, in that we make invalid parameters to the functions impossible by verifying them on beforehand and thus eliminating the need for a Result type in the output. It is also related to Hoare logic which analyzes programs in terms of preconditions and postconditions.

@sgraf812
Copy link
Collaborator

Yes, that makes a lot of sense and is how I should've designed the API in the first place. I'd be open to accept a PR in that regard!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants