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

Provide a way to get any values that exist in an Option.Option _ or none at all #18

Open
joneshf opened this issue May 7, 2020 · 0 comments
Labels
enhancement New feature or request

Comments

@joneshf
Copy link
Owner

joneshf commented May 7, 2020

The idea

I was talking to @gabejohnson the other day about using purescript-option to solve a problem. It came up that it'd be ideal if we could take an Option.Option _, and convert it to Data.Maybe.Just _ if any of the values exist or Data.Maybe.Nothing if none of them do. @gabejohnson mentioned that it'd be akin to Option.getAll, but only return Data.Maybe.Nothing if no values are there (as opposed to at least one value not being there). @gabejohnson also suggested Option.getSome as the name of the value.

The problem

While it seemed feasible at first, I think it's a value that would end up being hard to use. An example might help. Let's say we have:

type Greeting
  = Option.Option
      ( name :: String
      , title :: String
      )

What we want is a family of functions:

getSome ::
  Greeting ->
  Data.Maybe.Maybe
    {
    }
getSome ::
  Greeting ->
  Data.Maybe.Maybe
    { name :: String
    }
getSome ::
  Greeting ->
  Data.Maybe.Maybe
    { title :: String
    }
getSome ::
  Greeting ->
  Data.Maybe.Maybe
    { name :: String
    , title :: String
    }

We can probably write a typeclass and instance(s) for this family of functions. The hard part is using it. If we were to say:

greet ::
  Greeting ->
  Data.Maybe.Maybe ?option
greet option = Option.getSome option

What would we expect ?option to be? There's four valid choices for it, and if we choose the wrong one, we'll might get a Data.Maybe.Nothing when we didn't expect it . That's not really what we wanted. We wanted to take any Option.Option _, and only get a Data.Maybe.Nothing if none of the values were there.

The real implementation?

It almost seems like what we want is something like:

getSome ::
  Greeting ->
  Data.Maybe.Maybe
    ( Data.Variant.Variant
        ( name :: String
        , name_title ::
            { name :: String
            , title :: String
            }
        , title :: String
        )
    )

This way, we'll at least have a single type that is always the same. You'd be able to discriminate the cases dynamically instead of having to take a guess statically.

An alternative implementation

Instead of creating that family of functions, we can throw more Data.Maybe.Maybe _s in the mix and have a single function:

getSome ::
  Greeting ->
  Maybe
    { name :: Maybe String
    , title :: Maybe String
    }

I think this is actually more inline with the specific example @gabejohnson was dealing with. That seems like something we could throw together immediately as:

getSome ::
  forall option record.
  ToRecord option record =>
  Option option ->
  Data.Maybe.Maybe (Record record)
getSome option@(Option object)
  | Foreign.Object.isEmpty object = Data.Maybe.Nothing
  | otherwise = Data.Maybe.Just (toRecord option)

We can open up the Option.Option _ and check if the underlying Foreign.Object.Object _ is empty. If it is, there's no values, so we can return Data.Maybe.Nothing. Otherwise, we grab what we can.

My main qualm with this implementation is that anyone consuming it still has to handle the Data.Maybe.Just { name: Data.Maybe.Nothing, title: Data.Maybe.Nothing } case. That doesn't seem like it should be a possible value, but the types still allow it.

The current workaround

Assuming the Option.Option _ can have a Data.Eq.Eq _ instance, there's a way to get the alternative implementation without adding something to the Option module. Anyone can write the following value external to the Option module:

getSome ::
  forall option record.
  Data.Eq.Eq (Option.Option option) =>
  Option.ToRecord option record =>
  Option.Option option ->
  Data.Maybe.Maybe (Record record)
getSome option
  | option == Option.empty = Data.Maybe.Nothing
  | otherwise = Data.Maybe.Just (Option.toRecord option)

If the Option.Option _ has values without a Data.Eq.Eq _ instance, they can't use that value. It's not an end-all, but it should unstick while we try to figure things out here.

What to do?

I'd like to sit on this for a while. Each of the implementations above come with their own set of issues: hard to choose a type, illegal states represented, additional constraints.

@joneshf joneshf added the enhancement New feature or request label May 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant