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

Add Alternative docs #2572

Merged
merged 6 commits into from
Oct 20, 2018
Merged

Conversation

stephen-lazaro
Copy link
Contributor

Adds some docs on the Alternative type class covering:

  • Examples from basic collections
  • Examples from parsing (just sketched)
  • Examples using partition

@ceedubs
Copy link
Contributor

ceedubs commented Oct 20, 2018

Woohoo thanks @stephen-lazaro!

The Scala 2.11 build is failing with:

[tut] *** Error reported at /home/travis/build/typelevel/cats/docs/src/main/tut/typeclasses/alternative.md:104
// <console>:18: error: value toEither is not a member of scala.util.Try[Int]
//        def parseInt(s: String) = Try(s.toInt).toEither
//                                               ^

Luckily Cats provides syntax that's a little handier than going through Try anyway. You should be able to do either Either.catchNonFatal(s.toInt) or Either.catchOnly[NumberFormatException](s.toInt)

@stephen-lazaro
Copy link
Contributor Author

Ah I forgot about that syntax, thanks @ceedubs, will fix.

@codecov-io
Copy link

codecov-io commented Oct 20, 2018

Codecov Report

Merging #2572 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #2572   +/-   ##
=======================================
  Coverage   95.14%   95.14%           
=======================================
  Files         361      361           
  Lines        6630     6630           
  Branches      289      289           
=======================================
  Hits         6308     6308           
  Misses        322      322

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fcc0465...ebd096d. Read the comment docs.

@stephen-lazaro
Copy link
Contributor Author

Noticed a rendering wrinkle...
image
I'll fix that, but might be worth holding off until then.

@stephen-lazaro
Copy link
Contributor Author

Cool fixed:
image

Copy link
Contributor

@ceedubs ceedubs left a comment

Choose a reason for hiding this comment

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

I made some minor/nitpicky suggestions, but overall this is great. Thanks again, @stephen-lazaro!

}
```

As you might recall, `pure` wraps values in the context and `ap` allows us to do calculations in the context, while `combineK` allows us to combine, for any given type `A`, any two contextual values `F[A]` and `empty` provides the identity element for the combine operation.
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a lot in this sentence. Might want to consider breaking it up. At a minimum you might want to write it like the following to make it easier to mentally parse.

As you might recall, pure wraps values in the context; ap allows us to do calculations in the context; combineK allows us to combine, for any given type A, any two contextual values F[A]; and empty provides the identity element for the combine operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, good edit.


As you might recall, `pure` wraps values in the context and `ap` allows us to do calculations in the context, while `combineK` allows us to combine, for any given type `A`, any two contextual values `F[A]` and `empty` provides the identity element for the combine operation.

Of course, like other type classes, `Alternative` instances must obey some laws, in addition to those otherwise applying to `MonoidK` and `Applicative instances`:
Copy link
Contributor

Choose a reason for hiding this comment

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

My general rule is to eliminate "of course" and "obviously", because they hardly ever add additional meaning to a sentence and at worst they can make someone feel ignorant for not knowing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's a bad habit of mine.

* Right Distributivity: Applying the combination of two functions must be the combination of their applications.
* `(ff <+> fg) ap fa = (ff ap fa) <+> (fg ap fa)` where `ff: F[A => B]`, `fg: F[A => B]`, and `fa: F[A]`.

Morally, these laws guarantee the compatibility of the otherwise possibly independent `Applicative` and `MonoidK` structures.
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 be inclined to drop the "Morally, " from the beginning of this sentence.

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 👍


Then, we can implement an `Alternative` instance for this type like so:

```tut:book
Copy link
Contributor

Choose a reason for hiding this comment

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

It would probably look a little cleaner to mark this block as :silent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. Will do.

def parseIntFirstChar(s: String) = Either.catchNonFatal(2 * Character.digit(s.charAt(0), 10))

// Try first parsing the whole, then just the first character.
val decoder = Decoder.from(parseInt _) <+> Decoder.from(parseIntFirstChar _)
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 be inclined to put the lines above here in a :silent block (and maybe give them explicit return types).


The trick here is that the inner type constructor (essentially the replacement for `Boolean` as the target of our "predicate") must have a `Bifoldable` available. A great example of a `Bifoldable` is `Either`, and another is `Tuple2`.

Let's imagine that we're trying to make a bunch of independent possibly failure prone calls with a bunch of different `Int` inputs (say it's the id of a resource), each returning `Either[Int, Int]` where a left of an integer is the code modeling the failure we're given (say it's the HTTP code returned by a remote API we're calling), while right of an integer is the output of the calculation.
Copy link
Contributor

Choose a reason for hiding this comment

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

"failure prone" -> "failure-prone"

What do you think about using different types for the left and right (maybe Either[String, Int] or Either[Int, Long] to 1) show that they can be different types and 2) help the user keep track of which information is which?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. Will do.

docs/src/main/tut/typeclasses/alternative.md Show resolved Hide resolved
((requestResource _).pure[Vector] ap Vector(5, 6, 7, 99, 1200, 8, 22)).separate
```

Alternatively (no pun intended), in a totally different version of the same idea, we can lean on the instance of `Bitraverse` for `Tuple2` to try and partition cleanly the outcomes of two different calculations that each depend pairwise on the same input (maybe we have a primary key and we want to select both the remote id of the resources with that key, and those that are foreign key linked to the input key).
Copy link
Contributor

Choose a reason for hiding this comment

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

no pun intended

Hmm I'm not sure that I believe you 😆

Copy link
Contributor

Choose a reason for hiding this comment

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

"those that are foreign key" -> "those that are foreign keys" I think

val districtIds = regionsWithDistricts.separate._2.flatten
```


Copy link
Contributor

Choose a reason for hiding this comment

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

Some extra whitespace here.

@stephen-lazaro
Copy link
Contributor Author

Great, thanks for the edits @ceedubs! I believe this treats them.

Copy link
Contributor

@ceedubs ceedubs left a comment

Choose a reason for hiding this comment

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

🎉

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

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

Thanks!

@kailuowang kailuowang merged commit 9069dbb into typelevel:master Oct 20, 2018
@kailuowang kailuowang added this to the 1.5 milestone Oct 30, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants