-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Apply.applyN and Apply.mapN evaluate arguments in reverse #167
Comments
i can pick this up |
@bobbyrauchenberg thanks for volunteering! I suspect that other people will want to go forward with this, but let's give them a little bit of time to weigh in before you start the work. |
@ceedubs Sure |
I would've expected that methods like |
@ceedubs When you say Haskell has the opposite behavior, do you mean in some expression with performUnsafeIO? |
@refried sorry I forgot about this for a while. No, when I say that Haskell has the opposite behavior, I am not referring to cases of performUnsafeIO. Here is some Haskell code that is roughly equivalent to my example (disclaimer - I don't know Haskell very well): import Control.Applicative
main = liftA2 (\x y -> x : y : []) (1 <$ (putStrLn "1")) (2 <$ (putStrLn "2")) The output is:
and the returned value is A version of the Scala example that would line up a little better with this Haskell code might be something like: val a = Apply[Function0]
a.map2(a.map(() => println("1"))(_ => 1), a.map(() => println("2"))(_ => 2))(_ :: _ :: Nil)() which still prints 2 before 1 but then has the result of Note: Scala doesn't have a built-in IO type, so I'm using Function0 as a replacement. I agree that side effects not represented in the type system don't need to have a guaranteed execution order when using something like Does that make any sense? I seem to be alone in this matter, so maybe I'm being silly/ignorant. |
@refried I guess there could be an alternative point of view here that I guess that gets into the whole discussion of whether |
Sorry to spam this issue, but saying thoughts as they come to me: It does seem like it would be a bummer to not be able to traverse a list into |
At least in scalaz the constructed In any case, see scalaz/scalaz#886 |
@tpolecat yes. I'm not sure whether or not that's what I am saying, but it sounds like we are on the same page. |
Hi folks, I also haven't had coffee, and kind of forgot about this conversation too (double disclaimer) but if I understood @ceedubs correctly then I agree, So again I'm not saying apply should be right to left, but that the order shouldn't be something we rely on. In a pure program, the order doesn't matter; side effects is the only place this comes up. Function0 isn't really about side effects, but Task is, and Task has Wait I just saw the second comment about List traverse and IO. The resulting IO should execute in the left-to-right order of the List imo. Does it not? And these are delayed IO effects and not side effects, right? cheers |
@refried these are delayed IO effects (still side-effects though). The problem with what you are suggesting is that traverse only requires an applicative functor, not a Monad/FlatMap, so you can't rely on flatMap in your traverse implementation. Oh and regarding "does it not?". It does, due to a change that Erik made that relies on the reverse behavior of |
Sorry, I later noticed @ceedubs gave a few different viewpoints; I didn't mean to misrepresent. |
Never thought I'd be saying github is too slow compared to IRC! Hmm what about the rule of thumb "if your container supports sequencing, then use flatMap for its traverse"? List and IO and Task all can. Granted, I still haven't had coffee and I don't completely know what I'm talking about and I'm not at a compiler, but I will try to play with an example later to see if I can unconvince myself. |
@refried if you are traversing a |
@ceedubs You probably understood me right, and I am probably wrong, or at least confused because I'm pretty tired. Would you be willing to show an example that uses |
@refried Here is an example that uses scalaz.IO (with the help from @rossabaker's handy scataz project after adding a scalaz-effect dependency to it locally). I'm calling scala> import scalaz.effect.IO, cats._, cats.std._, scataz.toCats._
import scalaz.effect.IO
import cats._
import cats.std._
import scataz.toCats._
scala> val a = Apply[IO]
a: cats.Apply[scalaz.effect.IO] = scataz.toCats$$anon$1@4403dedd
scala> a.map2(a.map(IO(println("1")))(_ => 1), a.map(IO(println("2")))(_ => 2))(_ :: _ :: Nil).unsafePerformIO
2
1
res0: List[Int] = List(1, 2) Regarding "these are delayed IO effects (still side effects)": I don't know maybe I'm being loose with terminology here. I'm just saying that I'm doing |
@ceedubs Got it — thanks for the example, and the scataz link. import scalaz.effect.IO
import cats._
import cats.std._ // wouldn't work for me, I copy/pasted the listInstance instead
import scataz.toCats._
val ops = List(IO(println("1")), IO(println("2"))
val orderMatters = implicitly[Traverse[List]].sequence(ops) My thinking is that as long as this comes out in the right order, all is well. What do you think? scala> orderMatters.unsafePerformIO
1
2
res6: List[Unit] = List((), ()) |
It seems unintuitive to me to have a list traverse that guarantees left to right effect order by depending on Note that I'm fine with us visiting elements in the list in reverse order. I'm just saying that tracked side effects like Also I think throughout this whole PR I have been mixing up the fact that what's called |
For the record, I don't mind if it's swapped so that it is left-to-right, but my gut is worried about having a rule that it must be left-to-right. I'm not sure if this example is any good, but how would you feel if it was mapN over |
Yep, I think @ceedubs is correct. When a monad is available |
@tpolecat Sorry to ask, but are you sure? |
@tpolecat Or is there maybe some law that would settle it? |
Since this is dealing with side effects, I don't think this is the sort of thing that you can wrap a law around. @refried I kind of feel like we are stuck in a loop :(. If you want to say that no order is guaranteed in |
@ceedubs I'm leaning towards your side, I just don't understand it (and I may never, due to personal limitations) ;-) To point 1 (laws vs side effects): Would it help if we look at it in terms of the To point 2 (apply vs traverse): I started to wonder the same thing as I was posting, but I couldn't wrap my head around it. Is there some rule that says Is there another function like |
It's about effects. Try |
Proof by scalaz. It's ok with me though. Edit: Even if the order shouldn't be specified, scalaz's is fine. Whatever makes things easier to reason about is good anyway; as long as it doesn't cause some problem I'm not aware of. Edit 2: Don't mind me; I'm trying to learn, more than I am trying to say stuff. |
Err, wait! @ceedubs re "traverse only requires an applicative functor, not a Monad/FlatMap, so you can't rely on flatMap in your traverse implementation.": Although I was mixed up, and you're right, the from Haskell docs:
So OTOH, I feel like I should back out at this point, but I would love to understand the answers to (a) and (b). I dunno who else to tag in to ask. |
@refried I'm 100% for talking through this sort of stuff and learning more. I don't think I'm articulate enough to do it well over GitHub comments, though. Some time in the next few days I'll try to get a hold of you on Gitter and maybe we can discuss it some more there or on a google hangout or something. How does that sound? |
@ceedubs Same here, sounds great. |
Fixes typelevel#167 I think one thing that everyone agrees on is that if we are going to require a predictable order of effects with apply2, map2, traverse, etc that order should be left to right.
Example:
I would expect the "1" to print before the "2" in both cases. I suppose it is arbitrary and that as long as we are consistent, we could maybe keep it the way it is, but it does seem counterintuitive.
I believe that this was probably the reason that #142 existed in the first place. @non ended up changing Lists's
traverse
implementation, but I think changing the implementation ofmap2
would have been an alternative solution.What Cats is currently doing is the opposite of scalaz. My confusion in https://github.com/non/cats/pull/142#issuecomment-73452302 was because I had naively copied the Cats
traverse
implementation forList
into scalaz changingmap2
toapply2
not realizing the differences in semantics. I believe the current Cats implementation also does opposite of what Haskell does.I propose changing this to an evaluation order that matches the order of the arguments (and the scalaz precedent). Does anybody have a reason not to?
This could be done by changing the definition of
apply2
to:Similar changes would need to be made to
map2
, List'straverse
implementation, and perhaps some other places.The text was updated successfully, but these errors were encountered: