-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add Alternative docs #2572
Conversation
Woohoo thanks @stephen-lazaro! The Scala 2.11 build is failing with:
Luckily Cats provides syntax that's a little handier than going through |
Ah I forgot about that syntax, thanks @ceedubs, will fix. |
Codecov Report
@@ 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.
|
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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`: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 _) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Will do.
((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). |
There was a problem hiding this comment.
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 😆
There was a problem hiding this comment.
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 | ||
``` | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some extra whitespace here.
Great, thanks for the edits @ceedubs! I believe this treats them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Adds some docs on the
Alternative
type class covering: