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

[#100] Add Validation data type to Extra modules #106

Merged
merged 5 commits into from Oct 16, 2018
Merged

[#100] Add Validation data type to Extra modules #106

merged 5 commits into from Oct 16, 2018

Conversation

mauriciofierrom
Copy link
Contributor

Resolves #100

Add Validation data type to Extra modules.

Checklist:

HLint

  • I've changed the exposed interface (add new reexports, remove reexports, rename reexported things, etc.).
    • I've updated hlint.dhall accordingly to my changes (add new rules for the new imports, remove old ones, when they are outdated, etc.).
    • I've generated the new .hlint.yaml file (see this instructions).

General

  • I've updated the CHANGELOG with the short description of my latest changes.
  • All new and existing tests pass.
  • I keep the code style used in the files I've changed (see style-guide for more details).
  • I've used the stylish-haskell file.
  • My change requires the documentation updates.
    • I've updated the documentation accordingly.
  • I've added the [ci skip] text to the docs-only related commit's name.

@mauriciofierrom
Copy link
Contributor Author

It seems the tests are failing because Data.Bifoldable and Data.Bitraversable are not exported in GHC 8.0.2.

module Relude.Foldable.Reexport
       ( module Data.Foldable
       , module Data.Traversable
#if MIN_VERSION_base(4,10,0)
       , module Data.Bifoldable
       , module Data.Bitraversable
#endif
       ) where

@vrom911 vrom911 added new Bring something new into library (add function or type or interface) Hacktoberfest https://hacktoberfest.digitalocean.com/ labels Oct 15, 2018
@vrom911 vrom911 added this to In progress in #2: Hacktoberfest (October, 2018) via automation Oct 15, 2018
@vrom911
Copy link
Member

vrom911 commented Oct 15, 2018

@mauriciofierrom , thanks for your contribution!

Regarding the CI issue, I guess, the same CPP method (as in the Relude.Foldable.Reexport module) should be used in here 🙂

Copy link
Contributor

@chshersh chshersh left a comment

Choose a reason for hiding this comment

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

Looks good! I have several comments. And, as @vrom911 pointed out, we should use -XCPP here to have Bifoldable and Bitraversable instances only for higher base versions


{- |
Copyright: (c) 2014 Chris Allen, Edward Kmett
(c) 2018 Kowainic
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor typo here, should be Kowainik 🙂

liftA2 _ (Failure e) (Failure e') = Failure (e <> e')
liftA2 _ (Failure e) (Success _) = Failure e
liftA2 _ (Success _) (Failure e) = Failure e
liftA2 f (Success a) (Success a') = Success (f a a')
Copy link
Contributor

Choose a reason for hiding this comment

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

Just minor comment, but I think that instead of a' this variable could be named b to match type more precisely. The same trick cannot be done with e and e' because type variable is e in both cases. But I think that we can have e1 and e2 or el (e-left) and er (e-right). It's just my personal dislike of ' in names because according to common Haskell convention ' is usually used at the end of functions to mark it as strict (like foldl and foldl').

(*>) = liftA2 (flip const)

(<*) :: Validation e a -> Validation e b -> Validation e a
(<*) = liftA2 const
Copy link
Contributor

Choose a reason for hiding this comment

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

These are the default definitions of <* and *>. I wonder, if we can write them more efficiently if we do pattern-matching explicitly? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are both defined based on liftA2, which is a custom implementation and actually uses pattern-matching explicitly, so I thought perhaps it was best to just leave these methods for readibility's sake. I can change it anyway, WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep the default implementation here as well 👍

fold (Failure _) = mempty

foldMap :: Monoid m => (a -> m) -> Validation e a -> m
foldMap f = foldr (mappend . f) mempty
Copy link
Contributor

Choose a reason for hiding this comment

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

I think foldMap can be written more efficiently more via explicit pattern-matching

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kind of the same thing as with <* but in terms of foldr. I can change it anyway, WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, for <* and *> we're kinda hope for the inlining and optimizations and implementing <* as liftA2 const is not a big deal. But for foldMap optimizations might not trigger because the implementation is more difficult. So it's better to pattern-match here explicitly as well.

validationToEither :: Validation e a -> Either e a
validationToEither x = case x of
Failure e -> Left e
Success a -> Right a
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer using -XLabmdaCase language extension and write it like:

validationToEither = \case
    Failure e -> Left e
    Success a -> Right a


-- | Transform an 'Either' into a 'Validation'.
eitherToValidation :: Either e a -> Validation e a
eitherToValidation x = case x of
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here as for validationToEither

{-# INLINE fmap #-}
{-# INLINE (<$) #-}

instance Semigroup e => Applicative (Validation e) where
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be really great to add couple doctest tests to this instance to show different cases 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good 👍

, eitherToValidation
) where

import Data.Function (const)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think relude exports const, no need to have redundant import

@@ -0,0 +1,150 @@
{-# LANGUAGE InstanceSigs #-}
Copy link
Contributor

Choose a reason for hiding this comment

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

After reading this module I'm now even more sure that -XInstanceSigs should be enabled by default!

@@ -89,6 +89,7 @@ every module in your package by modifying your "Prelude" file:
@newtype@.
* __"Relude.Extra.Tuple"__: functions for working with tuples.
* __"Relude.Extra.Type"__: functions for inspecting and working with types.
* __"Relude.Extra.Validation"__: 'Validation' data type.
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot for updating the documentation! ❤️

@mauriciofierrom
Copy link
Contributor Author

It seems liftA2 is only available from base-4.10.0 so I need to use the CPP conditional here too. I defined (<*) & (*>) using it, so I either include the three functions in the conditional and leave the default implementations for (<*) & (*>), which are based on (<$), or rewrite them like @chshersh suggested. WDYT?

@chshersh
Copy link
Contributor

I defined (<) & (>) using it, so I either include the three functions in the conditional and leave the default implementations for (<) & (>), which are based on (<$), or rewrite them like @chshersh suggested. WDYT?

Hmm... Yeah, having the default implementation for *> and <* might not help because it won't work when there's no liftA2 inside Applicative and we can't implement liftA2 efficiently. In that case let's write <* and *> with manual pattern-matching.

-- | Examples:
--
-- >>> let fa = Success (*3) :: Validation Text (Int -> Int)
-- >>> let ga = Success (*4) :: Validation Text (Int -> Int)
Copy link
Contributor

Choose a reason for hiding this comment

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

For example purposes it's better to use [Text] instead of just Text. It's not a good idea to use Text as a monoidal error, so let's not show non-idiomatic examples in our documentation examples.

{-# INLINE fmap #-}
{-# INLINE (<$) #-}

-- | Examples:
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's a good idea to write this as ==== __Examples__ to make them expandable 🤔
Not sure how it will look like.

Also, IMO, for multiline haddock comments it's better to use: the following style

{- |
-}

so you won't need to add -- at the beginning of each line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The proposed expandable formatting works, but looks like this in Firefox and Chrome:

screenshot from 2018-10-16 06-41-50

It currently looks like this:

screenshot from 2018-10-16 07-13-33

WDYT? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that non-expandable is better 🙂

instance Traversable (Validation e) where
traverse :: Applicative f => (a -> f b) -> Validation e a -> f (Validation e b)
traverse f (Success a) = Success <$> f a
traverse _ (Failure e) = pure (Failure e)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can also implement sequenceA here?

#2: Hacktoberfest (October, 2018) automation moved this from In progress to Reviewer approved Oct 16, 2018
Copy link
Member

@vrom911 vrom911 left a comment

Choose a reason for hiding this comment

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

Amazing 🔥

Copy link
Contributor

@chshersh chshersh left a comment

Choose a reason for hiding this comment

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

Great change!

@chshersh chshersh merged commit 3e99f9c into kowainik:master Oct 16, 2018
#2: Hacktoberfest (October, 2018) automation moved this from Reviewer approved to Done: PR's Oct 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Hacktoberfest https://hacktoberfest.digitalocean.com/ new Bring something new into library (add function or type or interface)
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

None yet

3 participants