Skip to content

First Trial of Chapter4#7

Open
Itsukara wants to merge 2 commits intomasterfrom
my-solution
Open

First Trial of Chapter4#7
Itsukara wants to merge 2 commits intomasterfrom
my-solution

Conversation

@Itsukara
Copy link
Copy Markdown
Owner

@Itsukara Itsukara commented Dec 7, 2020

Solutions for Chapter 4

It would be very helpful if you could give me some comments on my solutions to Chapter 4.

cc @vrom911 @chshersh

Copy link
Copy Markdown

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

πŸŽ‰ Fantastic work!
Thank you so much for taking this course! I hope you had fun πŸ•Ί 🎀

We would really appreciate your feedback on the course!

You can also ask any questions and we can discuss any topics you would like to dig into as well πŸ™‚ Don't be a stranger!

Comment thread src/Chapter4.hs
Comment thread src/Chapter4.hs Outdated
(<*>) :: List (a -> b) -> List a -> List b
Empty <*> _ = Empty
_ <*> Empty = Empty
(Cons g gs) <*> (Cons x xs) = Cons (g x) (gs <*> xs)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There are two possible ways to implement the Applicative instance for the list, you choose the one where each function is only applied to the corresponding one element from the values list.
Another way to implement it is to apply each function to each element.

Unfortunately, in Haskell, it's not possible to implement two different instances of the same typeclass for the same data type, so you must choose one.

For list, it was chosen so the laws of Monad and Applicative typeclasses agree with each other. Because Applicative is a superclass for Monad there are some laws (implicit contract) that tells how functions must behave. For the Applicative-Monad relationship, the following must hold:

mf <*> mx ≑ mf >>= (\f -> mx >>= (\x -> return (f x)))

If you implement the Applicative instance the way you did here, it's not possible to implement the Monad instance to satisfy this rule.

However, it doesn't mean that such an instance is invalid. When you want to provide multiple instances in Haskell, you use newtypes. And there's the ZipList newtype that provides the described Applicative instance, and doesn't have the Monad instance as a consequence:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Thank you for comment. At first, I could no understand the difference between "only applied to the corresponding one element from the values list" and "apply each function to each element". But, by googling keywords "haskell cons list applicative", I found the related article and I understand what you mean i.e. when functions [f1, f2, f3] and values [x1, x2] are provided, [f1 x1, f1 x2, f2 x1, f2 x2, f3 x1, f3 x2] should be created.

I changed the code to following.

    (<*>) :: List (a -> b) -> List a -> List b
    Empty <*> _ = Empty
    _ <*> Empty = Empty
    (Cons f fs) <*> a = append (fmap f a) (fs <*> a) 

I used append in your suggestion in other comment.

BTW, your expression "mf <*> mx ≑ mf >>= (\f -> mx >>= (\x -> return (f x)))" is so difficult for me. I confirmed by example that your equation hold for List. But I could not understand why they are equal with following definition, where I assume f is just a function, not list of functions... After more than 10 minutes of consideration, I understand it at last. Anyway, it is still difficult for me ...

    l >>= f  = flatten (fmap f l)  

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BTW, your expression "mf <*> mx ≑ mf >>= (\f -> mx >>= (\x -> return (f x)))" is so difficult for me.

Don't worry! It's totally okay if you find it difficult. Haskell introduces too much new stuff... What I wanted to say, is that different expressions have different ways of writing them. But because in Haskell functions are pure by default, you can replace each function with its definition multiple times to expand expressions. This technique is called "Equational reasoning" and it can be used to prove various statements (as one I mentioned) or as an alternative form of debugging. Some information about it can be found in the following blog post:

So, in theory, with your definition of >>= as flatten (fmap f l) and your implementation of <*> it should be possible to prove that mf <*> mx has the same result as mf >>= (\f -> mx >>= (\x -> return (f x))). In practice, there're various libraries and testing frameworks that can do this for you, so you don't need to dive into math a lot and prove stuff manually.

Comment thread src/Chapter4.hs Outdated
Comment on lines +628 to +630
flatten Empty = Empty
flatten (Cons (Cons x Empty) xs) = Cons x (flatten xs)
flatten (Cons _ _) = Empty
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm afraid this implementation is not exactly correct. The way it's implemented, it keeps only singleton lists. Otherwise it returns Empty list. To implement flatter, try doing the following:

  1. Implement list append function first:
append :: List a -> List a -> List a
  1. Implement flatten after that, using append
flatten :: List (List a) -> List a
flatten Empty = ...
flatten (Cons x xs) = ...

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Thank you for suggestion. I changes as followings and used in definition of Applicative and Monad of List.

append :: List a -> List a -> List a
append Empty b       = b
append (Cons a as) b = Cons a (append as b) 

flatten :: List (List a) -> List a
flatten Empty = Empty
flatten (Cons x xs) = append x (flatten xs)

Comment thread src/Chapter4.hs Outdated
-}
andM :: (Monad m) => m Bool -> m Bool -> m Bool
andM = error "andM: Not implemented!"
andM fa fb = fa >>= (\a -> if a then fb else pure False)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That's a totally correct implementation! You can get rid of () here, but that's very minor. It already looks great πŸ‘πŸ»

Suggested change
andM fa fb = fa >>= (\a -> if a then fb else pure False)
andM fa fb = fa >>= \a -> if a then fb else pure False

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Thank you. I changed as your suggestion.

@Itsukara
Copy link
Copy Markdown
Owner Author

Itsukara commented Dec 14, 2020

I'm not sure what kind of naming convention for variables is the standard in Haskell. Could you teach me some reference for naming convention standard?

I will make another pull request after code change.

BTW, could you teach me why all pull request have failed?

@chshersh
Copy link
Copy Markdown

I'm not sure what kind of naming convention for variables is the standard in Haskell. Could you teach me some reference for naming convention standard?

Great timing for the question! πŸ˜… In @kowainik we published a blog post two days ago about standard naming conventions in Haskell:

BTW, could you teach me why all pull request have failed?

It's a bug in the setup-haskell GitHub Action. To fix this problem, you can rebase your solution on the latest version of code from main branch in our repository.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants