Hi all,
I'm trying to implement my own Result type (and yes, I'm aware you can abuse Either for this :-) ) but doing something as (seemingly?) simple as implementing a Functor instance was surprisingly difficult. data Result failure success = Success success | Failure failure instance Functor (Result failure) where fmap f (Success value) = Success (f value) fmap _ (Failure error) = Failure error -- fmap _ result@(Failure error) = result -- fmap _ result = result 1) Is it possible to define "Result" as "Result success failure" (instead of "Result failure success") and _still_ create an instance of Functor? 2) The two alternatives for fmap for the Failure scenario do not compile (the end result is "Result failure a" instead of "Result failure b") and that makes sense. But I would like to be able to express that "result" is not touched. Is there any way to do that? 3) And while wondering about that, is GHC smart enough to realize that "= Failure error" in the failure scenario is actually a NOP? (I'm just curious.) Cheers, Hilco _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
Hello Hilco,
On Sat, Mar 03, 2018 at 12:32:34PM -0800, Hilco Wijbenga wrote: > data Result failure success > = Success success > | Failure failure > > instance Functor (Result failure) where > fmap f (Success value) = Success (f value) > fmap _ (Failure error) = Failure error > -- fmap _ result@(Failure error) = result > -- fmap _ result = result > > 1) Is it possible to define "Result" as "Result success failure" > (instead of "Result failure success") and _still_ create an instance > of Functor? Yes, as far the compiler is concerned `data Result failure success` is equivalent to `data Result a b`. Same in your instance, you could have written: instance Functor (Result a) where -- etc. no problem. > 2) The two alternatives for fmap for the Failure scenario do not > compile (the end result is "Result failure a" instead of "Result > failure b") and that makes sense. But I would like to be able to > express that "result" is not touched. Is there any way to do that? You can but you have to modify your datatype! Probably you want something like this: data Result r f = Result r (ResState e) data ResState e = Ok | Error e > 3) And while wondering about that, is GHC smart enough to realize that > "= Failure error" in the failure scenario is actually a NOP? (I'm just > curious.) Not sure about this one, but I strongly suspect so! Was the explanation clear? -F _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
On Sat, Mar 3, 2018 at 1:40 PM, Francesco Ariis <[hidden email]> wrote:
> On Sat, Mar 03, 2018 at 12:32:34PM -0800, Hilco Wijbenga wrote: >> data Result failure success >> = Success success >> | Failure failure >> >> instance Functor (Result failure) where >> fmap f (Success value) = Success (f value) >> fmap _ (Failure error) = Failure error >> -- fmap _ result@(Failure error) = result >> -- fmap _ result = result >> >> 1) Is it possible to define "Result" as "Result success failure" >> (instead of "Result failure success") and _still_ create an instance >> of Functor? > > Yes, as far the compiler is concerned `data Result failure success` > is equivalent to `data Result a b`. Same in your instance, you could > have written: > > instance Functor (Result a) where > -- etc. > > no problem. But now "a" has a different meaning, doesn't it? I had the impression that the "Result a" was similar to currying or leaving a hole (something like " Result a * " but if I change the meaning of "a" from "failure" to "success" then things don't work anymore, do they? In any case, _when_ I flip "success" and "failure" the Functor instance no longer compiles. Which probably makes sense because I did not tell the compiler to interpret "Result failure" as "Result * failure"? >> 2) The two alternatives for fmap for the Failure scenario do not >> compile (the end result is "Result failure a" instead of "Result >> failure b") and that makes sense. But I would like to be able to >> express that "result" is not touched. Is there any way to do that? > > You can but you have to modify your datatype! Probably you want > something like this: > > data Result r f = Result r (ResState e) > data ResState e = Ok | Error e Ah, I see. Mmm, I'll have to think about that. I prefer the current setup. :-) _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
On Sat, Mar 03, 2018 at 06:31:47PM -0800, Hilco Wijbenga wrote:
> In any case, _when_ I flip "success" and "failure" the Functor > instance no longer compiles. Which probably makes sense because I did > not tell the compiler to interpret "Result failure" as "Result * > failure"? I wonder if you are talking about failure (type parameter) or Failure (data constructor). This instance obviously work instance Functor (Result success) where fmap f (Success value) = Success (f value) fmap _ (Failure error) = Failure error Flipping in `data` of course means you are to flip one of: a) instance or b) data constructor, e.g.: instance Functor (Result success) where fmap f (Failure error) = Failure (f error) fmap _ (Success value) = Success value _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
On Sat, Mar 3, 2018 at 6:41 PM, Francesco Ariis <[hidden email]> wrote:
> On Sat, Mar 03, 2018 at 06:31:47PM -0800, Hilco Wijbenga wrote: >> In any case, _when_ I flip "success" and "failure" the Functor >> instance no longer compiles. Which probably makes sense because I did >> not tell the compiler to interpret "Result failure" as "Result * >> failure"? > > I wonder if you are talking about failure (type parameter) or > Failure (data constructor). I believe I am talking about "failure" the type parameter. > This instance obviously work > > instance Functor (Result success) where > fmap f (Success value) = Success (f value) > fmap _ (Failure error) = Failure error > > Flipping in `data` of course means you are to flip one of: > a) instance or b) data constructor, e.g.: > > instance Functor (Result success) where > fmap f (Failure error) = Failure (f error) > fmap _ (Success value) = Success value Yes, indeed. But that's what I meant with 'now "a" has a different meaning'. I understand that to the compiler there is no practical difference between Result a b and Result b a ... but there is to me. :-) So am I to understand then that to be able to do the kind of "fmap" I want (i.e. one that affects the "success" value), I _have to_ make sure that I use "Result failure success" (and not "Result success failure")? _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
That is correct. You can think of type parameters as being curried and applied the same way as function parameters. Since application is strictly left to right, and we have no type-level flip function (barring newtype), getting your type down to the single parameter allowed in a Functor instance can only ever mean using the rightmost parameter in the type's definition. This is just a semantic limitation of how Haskell types are expressed. On Mar 3, 2018 7:56 PM, "Hilco Wijbenga" <[hidden email]> wrote: On Sat, Mar 3, 2018 at 6:41 PM, Francesco Ariis <[hidden email]> wrote: _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
In reply to this post by Hilco Wijbenga
> I'm trying to implement my own Result type (and yes, I'm aware you can
> abuse Either for this :-) ) but doing something as (seemingly?) simple > as implementing a Functor instance was surprisingly difficult. > Without knowing your final use case are Bifunctors what you are after (https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Bifunctor.html)? I used them when I wanted to fmap Left to convert errors to other errors. -Sumit _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
On Sat, Mar 3, 2018 at 11:34 PM, Sumit Raja <[hidden email]> wrote:
>> I'm trying to implement my own Result type (and yes, I'm aware you can >> abuse Either for this :-) ) but doing something as (seemingly?) simple >> as implementing a Functor instance was surprisingly difficult. >> > Without knowing your final use case are Bifunctors what you are after > (https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Bifunctor.html)? > I used them when I wanted to fmap Left to convert errors to other > errors. No, they provide too much power. [I don't want to do anything more than die on the first error although I may add support for multiple errors later.] And I want to do this myself! No doubt, once I'm (far) more familiar with Haskell I'll be able to use things like Either and BiFunctor but right now they are just a step too far. I get lost in a forest of "weird" operators. :-) The purpose really is to work on something I fully understand (a Result type) and then see what I need to do in Haskell to support that. I just felt that the logical/intuitive type (at least to me) is "Result success failure" (it's the success value I really care about, after all) but that does not work with Functor (or Applicative, or Monad), AFAICT. So the order ("failure" first) seems to be forced on me. So be it. P.S. The second line in the Data.Bifunctor documentation made me chuckle: "Intuitively it is a bifunctor where both the first and second arguments are covariant.". Intuitive? Really? :-) ;-) _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
In reply to this post by Hilco Wijbenga
Hilco, I am going to use a lot of Haskell concepts here, but I am a strong believer that using the correct terms empowers the learner to go out and continue their education on their own. Your first question is is it possible to define Result as Result success failure and still create an instance of Functor? The answer to that is yes. We can look up the definition of the Functor typeclass (use :i Functor in GHCI), and we can see the following: class Functor (f :: * -> *) where.... From that definition, we can see that we can have an instance of Functor for any type that has the kind * -> *. It seems like you are familiar with treating type constructors as functions at the type level, so what we are saying here is that in order for a type constructor to be a functor, it has to be a type constructor that takes one type constant to yield another type constant. Your type Result has the kind * -> * -> *. It takes two type constants, so we know that wont "fit" into Functor, and we have to partially apply the Result type constructor to get another type constructor with the kind * -> *, which can have an instance of Functor. What I think is missing from your understanding is this - if you change your data declaration to be Result success failure, then your instance of functor will only be able to modify the failure case. Why? Because when you write your instance of functor, it will look like this: instance Functor (Result success) where.... If we look at the signature of fmap :: (a -> b) -> f a -> f b, and then we specialize so that f ~ Result success, we get fmap :: (a -> b) -> Result success a -> Result success b. In other words, you have already baked in the first type argument to Result when you are writing your instance of Functor for it, and are not allowed to change it anymore. Your second question I would like to be able to express that "result" is not touched... I am assuming here that you are talking about this piece of code "fmap _ result@(Failure error) = result" using your original functor instance and data Result failure success... We can't express that result is not touched. Why? Because result is touched. It has to be. As you yourself mentioned, on the left-hand side we have a value of one type, and on the right-hand side we have a value of a different type. Even though we don't witness the change at the value level, we do have a change at the type level. So we can't express it because it is not what is happening. Now, I get what you are saying - the result on the left-hand side has the same data as the right-hand side, same data constructor, etc, but it is still not the same value. On your third question, regarding GHC being smart enough to realize a NOP, I don't know. I hope this helps! On Sat, Mar 3, 2018 at 2:32 PM, Hilco Wijbenga <[hidden email]> wrote: Hi all, _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
In reply to this post by Hilco Wijbenga
> 3) And while wondering about that, is GHC smart enough to realize that
> "= Failure error" in the failure scenario is actually a NOP? (I'm just > curious.) Yes it does. The STG CSE pass handles this. Sylvain _______________________________________________ Beginners mailing list [hidden email] http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners |
Free forum by Nabble | Edit this page |