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

Refactor entity constraint parsing in Quasi module #1315

Merged

Conversation

danbroooks
Copy link
Contributor

@danbroooks danbroooks commented Sep 7, 2021

Before submitting your PR, check that you've:

  • Documented new APIs with Haddock markup
  • Added @since declarations to the Haddock
  • Ran stylish-haskell on any changed files.
  • Adhered to the code style (see the .editorconfig file for details)

After submitting your PR:

  • Update the Changelog.md file with a link to your PR
  • Bumped the version number if there isn't an (unreleased) on the Changelog
  • Check that CI passes (or if it fails, for reasons unrelated to your change, like CI timeouts)

A further contribution towards #1156, addresses the fold referenced in that issue by creating a type that holds the various possible constraints, and can be built up and merged into one. I have used Monoids / Semigroups however the instances are not lawful. I will put some further detail about this as a comment in the relevant area in the PR.

I have also renamed splitExtras to parseEntityFields as this is more descriptive I think. This function is called when the fields of an entity are extracted, the first step being to split the 'extra' tokens from the rest of the entity fields which are added to a value of type M.Map Text [ExtraLine], where the key is the entity name and its [ExtraLine] values are the entity fields.

just1 :: (Show x) => Maybe x -> Maybe x -> Maybe x
just1 (Just x) (Just y) = error $ "expected only one of: "
`mappend` show x `mappend` " " `mappend` show y
just1 x y = x `mplus` y
Copy link
Contributor Author

@danbroooks danbroooks Sep 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with using Semigroups/Monoids for this change, but this implementation of just1 which is used in the semigroup instance makes it unlawful. I could re-write this to not use these typeclasses, but I thought it was a nice refactoring (means I could use foldMap, mempty).

Alternatively this could use something like First or Last, though it is probably a nicer user experience to receive this error than for it to silently proceed using one of the values over the other in the case of duplicate values. In fact that is definitely not what we would want.

It may be that now I have done an initial refactor, I could tweak things further again to not have unlawful instances, and use something instead foldMap / mempty

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm! I like using the Monoid and Semigroup instances here. That's neat. But I don't like having a Semigroup operation that will error on a relatively common case - that seems like a footgun.

I feel like a type that better represents this is something like:

data SetOnce a 
    = SetOnce a 
     | NotSet 
     | SetManyTimes (NonEmpty a)

instance Semigroup (SetOnce a) where
    NotSet <> a = a

    SetOnce a <> NotSet = SetOnce a
    SetOnce a <> SetOnce b = SetManyTimes (a :| [b])
    SetOnce a <> SetManyTimes as = SetManyTimes (NEL.cons a as)

    a@(SetManyTimes xs) <> NotSet = a
    SetManyTimes xs <> SetOnce a = SetManyTimes (xs `snoc` a)
    SetManyTimes xs <> SetManyTimes ys = SetManyTimes (xs <> ys)

instance Monoid (SetOnce a) where
    mempty = NotSet

Rendering/erroring can then happen outside of the code.

@danbroooks danbroooks marked this pull request as ready for review September 7, 2021 18:25
just1 :: (Show x) => Maybe x -> Maybe x -> Maybe x
just1 (Just x) (Just y) = error $ "expected only one of: "
`mappend` show x `mappend` " " `mappend` show y
just1 x y = x `mplus` y
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm! I like using the Monoid and Semigroup instances here. That's neat. But I don't like having a Semigroup operation that will error on a relatively common case - that seems like a footgun.

I feel like a type that better represents this is something like:

data SetOnce a 
    = SetOnce a 
     | NotSet 
     | SetManyTimes (NonEmpty a)

instance Semigroup (SetOnce a) where
    NotSet <> a = a

    SetOnce a <> NotSet = SetOnce a
    SetOnce a <> SetOnce b = SetManyTimes (a :| [b])
    SetOnce a <> SetManyTimes as = SetManyTimes (NEL.cons a as)

    a@(SetManyTimes xs) <> NotSet = a
    SetManyTimes xs <> SetOnce a = SetManyTimes (xs `snoc` a)
    SetManyTimes xs <> SetManyTimes ys = SetManyTimes (xs <> ys)

instance Monoid (SetOnce a) where
    mempty = NotSet

Rendering/erroring can then happen outside of the code.

pure <$> takeUniq ps "" defs (n : rest)
}
_ ->
mempty
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much nicer 👍🏻

@danbroooks danbroooks force-pushed the quasi-refactor-entity-constraints branch from 1f539dd to 3318d91 Compare January 29, 2022 21:32
@parsonsmatt parsonsmatt merged commit 27ae9b5 into yesodweb:master Mar 14, 2022
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

Successfully merging this pull request may close these issues.

2 participants