Skip to content

Deprecate .:? and add notes to .:! and .!= about use #64

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

Closed
wants to merge 1 commit into from

Conversation

thomashoneyman
Copy link
Contributor

What does this pull request do?

Closes #63 by deprecating the .:? operator and updating documentation to steer folks away from .:! and .!= except in the (seemingly quite rare) case in which their behavior is desired rather than the behavior of .: for its Maybe instance.

#63 describes why .:? is unnecessary, and #47 and #32 describe why it was introduced in the first place and why .:! should not be promoted as the most common way to decode an optional field.

#63 also describes why .!= is unnecessary when using .: to get fields; I would like to have deprecated it in this pull request, but it is still useful when using .:!, so I've kept it in.

I did add a note about it only being required with .:! and not with .:, where the better <|> pure default can be used instead. I would like to promote using <|> instead because it's a natural step from there to realize you can supply multiple ways to parse a field and only take the first successful decoded value.

Where should the reviewer start?

All code changes are in Decode, and the tests have updated to use the new operators.

Other Notes:

The project tutorial should be updated to introduce <|> pure default earlier, rather than introducing .!= first.

@thomashoneyman thomashoneyman added the type: enhancement A new feature or addition. label Sep 8, 2019
@thomashoneyman thomashoneyman self-assigned this Sep 8, 2019
@thomashoneyman
Copy link
Contributor Author

Oh, no: should have looked more closely, but the Maybe instance is not the same as .:?, and it isn't the same as .:!, either. Instead, here's the situation:

  1. The Maybe instance tries to decode a given value using its DecodeJson instance, so long as that value is not null
instance decodeJsonMaybe :: DecodeJson a => DecodeJson (Maybe a) where
  decodeJson j
    | isNull j = pure Nothing
    | otherwise = Just <$> decodeJson j
  1. .: tries to look up a key in an object, failing with a decode error if the key is absent. If the key is present, then it tries to decode the value.
getField :: forall a. DecodeJson a => FO.Object Json -> String -> Either String a
getField o s =
  maybe
    (Left $ "Expected field " <> show s)
    (elaborateFailure s <<< decodeJson)
    (FO.lookup s o)

infix 7 getField as .:
  1. .:! tries to look up a key in an object, and if it can find that key, it tries to decode the value within by using its DecodeJson instance. Notably, because this is not using the Maybe instance for DecodeJson, this will fail on null where the Maybe instance will not.
getFieldOptional :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional o s =
  maybe
    (pure Nothing)
    decode
    (FO.lookup s o)
  where
    decode json = Just <$> (elaborateFailure s <<< decodeJson) json

infix 7 getFieldOptional as .:!
  1. .:? tries to look up a key in an object. If it can find it, then it checks whether its value is null (like the Maybe instance does) and only if it is non-null does it try to decode the value.
getFieldOptional' :: forall a. DecodeJson a => FO.Object Json -> String -> Either String (Maybe a)
getFieldOptional' o s =
  maybe
    (pure Nothing)
    decode
    (FO.lookup s o)
  where
    decode json =
      if isNull json
        then pure Nothing
        else Just <$> (elaborateFailure s <<< decodeJson) json

infix 7 getFieldOptional' as .:?

So, in summary, we have a number of cases:

  1. If the key must be present in the object and its value must exist, then .: works
  2. If the key must be present in the object, but the decoded type is optional, then .: with its Maybe instance works (null becomes Nothing).
  3. If the key is optional, and the value is optional (null == Nothing rather than a decode error), then .:? works
  4. If the key is optional, but the value cannot be null, then .:! works

Further confusing the issue: if you are using .: then you can use Alternative's <|> to provide a fallback in case decoding fails. That means any of:

a) the key is missing
b) the value is null
c) the value could not be decoded

...but you can't recreate the case where you want decoding to fail if the value does not satisfy the decoder, but to decode to Nothing if the key is absent.

For these reasons, I'm going to close this PR and use #63 for further discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A new feature or addition.
Development

Successfully merging this pull request may close these issues.

Deprecate .:? (getFieldOptional) and .!= (defaultField)
2 participants