Conversation
chshersh
left a comment
There was a problem hiding this comment.
π 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!
| (<*>) :: List (a -> b) -> List a -> List b | ||
| Empty <*> _ = Empty | ||
| _ <*> Empty = Empty | ||
| (Cons g gs) <*> (Cons x xs) = Cons (g x) (gs <*> xs) |
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
| flatten Empty = Empty | ||
| flatten (Cons (Cons x Empty) xs) = Cons x (flatten xs) | ||
| flatten (Cons _ _) = Empty |
There was a problem hiding this comment.
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:
- Implement list append function first:
append :: List a -> List a -> List a- Implement
flattenafter that, usingappend
flatten :: List (List a) -> List a
flatten Empty = ...
flatten (Cons x xs) = ...There was a problem hiding this comment.
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)
| -} | ||
| 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) |
There was a problem hiding this comment.
That's a totally correct implementation! You can get rid of () here, but that's very minor. It already looks great ππ»
| andM fa fb = fa >>= (\a -> if a then fb else pure False) | |
| andM fa fb = fa >>= \a -> if a then fb else pure False |
There was a problem hiding this comment.
Thank you. I changed as your suggestion.
|
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? |
Great timing for the question! π In @kowainik we published a blog post two days ago about standard naming conventions in Haskell:
It's a bug in the |
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