-
Notifications
You must be signed in to change notification settings - Fork 195
Ch 7 Exercise Groups #1 and #2 are complete #126
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
Ch 7 Exercise Groups #1 and #2 are complete #126
Conversation
exercises/chapter7/test/Main.purs
Outdated
suite "Write a function combineMaybe" do | ||
test "Test Maybe of Maybe Int with Just" | ||
$ Assert.equal (Just (Just 1)) | ||
$ combineMaybe (Just $ Just 1) |
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.
Interesting test. I guess this shows nested Maybe of Maybe remains unchanged. In that case, I think it would be clearer to write both of these with identical syntax: pick either parens or $
.
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.
@milesfrain I removed the test per your comment below.
exercises/chapter7/test/Main.purs
Outdated
$ combineMaybe (Nothing :: Maybe (List Int)) | ||
test "Test List of Maybe Int with mix of Just and Nothing" | ||
$ Assert.equal (Just (Just 1) : Just Nothing : Just (Just 3) : Nil) | ||
$ combineMaybe (Just $ Just 1 : Nothing : Just 3 : Nil) |
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 don't know if picking Maybe for our Applicative is the best idea, as far as letting readers easily reason through these tests. Keeping things simple with structures like Array and List might be better.
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.
@milesfrain you bring up an interesting idea that didn't occur to me until now:
I was wondering what the payoff of the combineMaybe
exercise was. Now I realize that the payoff is combineMaybe
accomplishes what combineList
does for List
, but for all other types of collections as well (Array
, etc). It would have helped me to understand the exercise if it had explained that this would be the justification for combineMaybe
. Now the regression tests have a reason to do both List
and Array
as you imply.
What do you think of adding briefly explaining the advantage of combineMaybe
over combineList
in the exercise? I'd get to that when I implement the changes for the master
branch. Or do the unit tests you suggested accomplish the same thing?
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.
combineMaybe
accomplishes whatcombineList
does forList
, but for all other types of collections as well (Array
, etc).
Not quite. combineList
also works for Array
and other Applicatives. In the signatures below, you can substitute Array
for f
.
combineList :: forall a f. Applicative f => List (f a) -> f (List a)
combineMaybe :: forall a f. Applicative f => Maybe (f a) -> f (Maybe a)
sequence
is what accomplishes everything that both combineMaybe
and combineList
do. t
just needs to be a Traversable
, which both List
and Maybe
are.
combineList :: forall a f . Applicative f => List (f a) -> f (List a)
combineMaybe :: forall a f . Applicative f => Maybe (f a) -> f (Maybe a)
sequence :: forall a f t. Applicative f => Traversable t => t (f a) -> f (t a)
listArrayInt :: List (Array Int)
listArrayInt = [ 1, 2 ] : [ 3 ] : Nil
maybeArrayInt :: Maybe (Array Int)
maybeArrayInt = Just [ 1, 2 ]
main :: Effect Unit
main = do
logShow $ combineList listArrayInt
logShow $ sequence listArrayInt
logShow $ combineMaybe maybeArrayInt
logShow $ sequence maybeArrayInt
{-
Prints:
[(1 : 3 : Nil),(2 : 3 : Nil)]
[(1 : 3 : Nil),(2 : 3 : Nil)]
[(Just 1),(Just 2)]
[(Just 1),(Just 2)]
-}
So an alternate solution for the combineMaybe
exercise is:
combineMaybe :: forall a f. Applicative f => Maybe (f a) -> f (Maybe a)
combineMaybe = sequence
Although I think it's more useful to write a custom version as you have done. Turns out that your answer is identical to sequence in the Traversable instance for Maybe.
Another interesting point is that if you search for a function that matches the signature of combineMaybe
on pursuit, you find sequence
.
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.
Wow, nice input. It's interesting that the sequence
feature comes in the section after this exercise; hence I agree w/ you about keeping the "custom version".
I think the unit tests are sufficient to illustrate the generality of combineMaybe
.
lift3 f x y z = f <$> x <*> y <*> z | ||
|
||
combineMaybe :: ∀ a f. Applicative f => Maybe (f a) -> f (Maybe a) | ||
combineMaybe (Just x) = Just <$> x |
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.
Looks good.
I often forget infix <$>
means map
. In this case, just using map
might be clearer (and is same character count).
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.
@milesfrain Agreed; updated
e8757dc
to
d9f6d33
Compare
fd9fd9a
to
a36fec7
Compare
@milesfrain I'm trying to write unit tests to test the validation routine solutions which I've posted to discourse. If you have time, could you take a look? |
Posted a solution. |
@milesfrain Thanks for your solution and your suggestion; I'll push on my branch next time. Painful not seeing the solution. |
@milesfrain I'm stuck at a conceptual level on how to make the Exercise 1 in the Exercise Group 3 work as a unit test. In my last commit, I uploaded my current work copy as discussed in the commit two commits above. I "borrowed" hrk's solution for this exercise. He is using Now I want to find a way to capture the output he's sending to the console and route it to a unit test. Here's where I'm stuck conceptually, I can't figure out how to do it. I'm stuck on what to substitute for I've included my incorrect solution for the test. I know that |
You’ll want to use an Applicative which allows you to keep track of the order in which things happened. Have you come across the Writer monad? Essentially it is
(so you need w to be a Monoid.) This allows you to keep track of what order things happened in by using a monoid such as Array. For instance, if we define
and then try |
Incidentally Writer is also a monad, which is why it is usually called “the Writer monad”, but we only care about its applicative structure here. |
That's in Ch11, but I think it's fine to use advanced features in the tests. Feel free to table writing a test for the latter part of that exercise, and we'll add a note to circle-back to it after completing Ch11. For the first part of that exercise with the suite "Exercise 1: Traverse Tree" do
let
sqrtPosInt :: Int -> Maybe Number
sqrtPosInt i | i < 0 = Nothing
sqrtPosInt i = i # toNumber # sqrt # Just
treePos = Branch (Branch Leaf 1 Leaf) 1225 (Branch Leaf 4 Leaf)
treeNeg = Branch (Branch Leaf 1 Leaf) (-2) (Branch Leaf 4 Leaf)
expectPos = Just (Branch (Branch Leaf 1.0 Leaf) 35.0 (Branch Leaf 2.0 Leaf))
expectNeg = Nothing
test "sqrtPosInt Pos"
$ Assert.equal expectPos
$ traverse sqrtPosInt treePos
test "sqrtPosInt Neg"
$ Assert.equal expectNeg
$ traverse sqrtPosInt treeNeg Note that the tests will require writing an instance of |
The subject of chapter 11 is monad transformers, which are definitely at least an ‘intermediate’ level concept, but the simplified form of Writer I described above (which is not a monad transformer) should definitely be manageable for anyone who has already come this far in the book. Come to think of it, you might have noticed that Writer is the same as Tuple, and I just checked and Tuple has the exact same Applicative instance as the one I described above, so there’s actually no need to introduce a new Writer type; we can just use Tuple. |
@hdgarrood @milesfrain Thanks for your suggestions; I'll explore them. I'm tempted to try the |
Closing this PR and replacing with #146. |
Sounds good.
|
@milesfrain I thought about it, but there were so many non-chapter7 files that it was just easier to pull the chapter7 files from this PR into a new PR. I like your tactic though. |
issue #109
@milesfrain Could you review exercise 3 in group 1. I think I have it, but not sure.