Skip to content

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

Conversation

oldfartdeveloper
Copy link

@oldfartdeveloper oldfartdeveloper commented Apr 30, 2020

issue #109

@milesfrain Could you review exercise 3 in group 1. I think I have it, but not sure.

@milesfrain milesfrain mentioned this pull request Apr 30, 2020
suite "Write a function combineMaybe" do
test "Test Maybe of Maybe Int with Just"
$ Assert.equal (Just (Just 1))
$ combineMaybe (Just $ Just 1)
Copy link
Member

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 $.

Copy link
Author

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.

$ 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)
Copy link
Member

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.

Copy link
Author

@oldfartdeveloper oldfartdeveloper May 1, 2020

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?

Copy link
Member

Choose a reason for hiding this comment

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

combineMaybe accomplishes what combineList does for List, 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.

Copy link
Author

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
Copy link
Member

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).

Copy link
Author

Choose a reason for hiding this comment

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

@milesfrain Agreed; updated

@oldfartdeveloper
Copy link
Author

@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?

@milesfrain
Copy link
Member

@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.
For future troubleshoot efforts, please push to a branch that I can checkout to quickly reproduce the issue that you're seeing. I think that would save time for both of us copying and pasting code to and from the post.
You could even just push to this branch and leave a note that you're having trouble getting things to compile, then I could make a PR with the fix.

@oldfartdeveloper
Copy link
Author

@milesfrain Thanks for your solution and your suggestion; I'll push on my branch next time. Painful not seeing the solution.

@oldfartdeveloper oldfartdeveloper changed the title Ch 7 Exercise Group #1 is complete Ch 7 Exercise Groups #1 and #2 are complete May 3, 2020
@oldfartdeveloper
Copy link
Author

@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 logShow to print his solution to the console. I've run this code myself here and it runs fine.

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 logShow; ideally I'd like to output each line that logShow prints as an entry in a List.

I've included my incorrect solution for the test. I know that logShow on line 159 is preposterous, but what should I use instead? How do I capture the log output instead in a way that I can pass as the "actual" value for a unit test comparison?

@hdgarrood
Copy link

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 data Writer w a = Writer w a, with apply defined as:

apply (Writer w1 f) (Writer w2 x) = Writer (w1 <> w2) (f x)

(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

tell :: w -> Writer w Unit
tell w = Writer w unit

and then try traverse (\x -> tell [x]) and see what you end up with.

@hdgarrood
Copy link

hdgarrood commented May 5, 2020

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.

@milesfrain
Copy link
Member

milesfrain commented May 5, 2020

Have you come across the Writer monad?

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 Traverseable instance, here's a test to use:

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 Show and Eq for Tree. Not sure if those should be provided, or considered "bonus review" exercises for the reader.

@hdgarrood
Copy link

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.

@oldfartdeveloper
Copy link
Author

@hdgarrood @milesfrain Thanks for your suggestions; I'll explore them. I'm tempted to try the Tuple approach suggested.

@oldfartdeveloper
Copy link
Author

Closing this PR and replacing with #146.

@milesfrain
Copy link
Member

Closing this PR and replacing with #146.

Sounds good.
Another option if you wanted to keep everything in this PR is to:

  • Do an interactive rebase (rebase -i) and mark the series of commits as fixups
  • Do a force push to this branch: git push -f <your-remote-name> ch7-solutions

@oldfartdeveloper
Copy link
Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants