tfausak / flow

:droplet: Write more understandable Haskell.
https://hackage.haskell.org/package/flow
MIT License
198 stars 10 forks source link

Allow mixing operators? #3

Closed tfausak closed 8 years ago

tfausak commented 9 years ago

http://www.reddit.com/r/haskell/comments/324415/write_more_understandable_haskell_with_flow/cq8mehu

>>> x |> f <| y

<interactive>:1:1:
    Precedence parsing error
        cannot mix ‘|>’ [infixl 0] and ‘<|’ [infixr 0] in the same infix expression

I think it's reasonable to treat x |> f <| y as (x |> f) <| y.

tfausak commented 9 years ago

For a more concrete example, how should 2 |> subtract <| 3 be parsed?

(2 |> subtract) <| 3
-- subtract 2 3
-- 1

2 |> (subtract <| 3)
-- subtract 3 2
-- -1

I think the former makes the most sense. It's the same as this:

2 `subtract` 3
-- subtract 2 3
-- 1

In fact, this is apparently a way to write functions as infix in F#. http://stackoverflow.com/questions/16548654/how-to-write-an-infix-function

tfausak commented 9 years ago

Is there a compelling argument to be made for how <. and .> should mix? What should f .> g <. h mean?

(f .> g) <. h
-- h .> f .> g

f .> (g <. h)
-- f .> h .> g
tfausak commented 9 years ago

I think the former makes more sense, but only barely. "f and then g but first h" is ambiguous, but "(f and then g) but first h" makes the most sense to me.

tfausak commented 8 years ago

After thinking about this for a while, I want to add this feature. It makes a lot of sense to be able to mix these operators.

tfausak commented 8 years ago

Changed my mind after implementing this: https://github.com/tfausak/flow/pull/7#issuecomment-145395343

sid-kap commented 8 years ago

I understand that you've already made a decision on this, but anyways here's a different reason to maybe change the precedence of |> to infixl 1.

I originally wrote something like the following:

x = 
  thing
  >>= someAction
  >>= anotherAction
  &   fmap something
  >>= anotherAction

This associates like (((thing >>= someAction) >>= anotherAction) & fmap something) >>= anotherAction.

If I replace & with |>, due to precedence rules (since |> has precedence 0), it breaks. It associates it as:

x = 
  (thing
  >>= someAction
  >>= anotherAction)
  |>  (fmap something
  >>= anotherAction)

which isn't what I intended (it doesn't even typecheck).

If we changed the precedence of |> to 1, then my example would work. The best I can do, using the current precedences, is

x = 
  thing
  |> (>>= someAction)
  |> (>>= anotherAction)
  |>  fmap something
  |> (>>= anotherAction)

which is too ugly in my opinion.

Further justification for mixing |> and >>= (as well as other "application-like" operators like <&>): Since they're all "application-like" operators, it's quite clear that you're taking one thing and applying a sequence of operations on it. (Note that currently, &, <&>, and >>= are infixl 1, and they play nicely together.)


As a side note, the base people chose to give & precedence 1 and $ precedence 0 precisely so that you can mix & and $.

I'm personally not in favor of mixing |> and <|, but it might add some perspective that the base people were okay with it. Even if we think mixing |> and <| is bad behavior, maybe it's okay to allow mixing, but still discourage people from doing that. If they shoot themselves in the foot with it, that's their fault.


Anyways, I understand that you've already made a decision on this, and this might not be a good enough reason to change your mind.

tfausak commented 8 years ago

Thanks for the comment and detailed motivation! I have to admit that when I decided about this I was only thinking about Flow, not the rest of the ecosystem. That being said, I personally prefer to avoid expressions like your example. I would much rather do this:

x = do
  a <- someAction thing
  b <- anotherAction a
  let c = something b
  anotherAction c

The fact that Flow doesn't play nice with your example is an accident. But I actually like the code it encourages you to write.

Flow isn't meant to be a drop-in replacement for anything. I know I have a cheat sheet in the documentation, but it's not meant to imply that |> is exactly the same as &. Otherwise what would be the point? If you want |> to be infixl 1, you can get it from Data.Functor.Monadic.

sid-kap commented 8 years ago

Thanks for your response! In retrospect, I think your way of doing this is better in most cases.

In case you're curious, the situation that prompted me to write code like I mentioned above was when I was using xml-conduit (the XML-parsing library), which I don't think is very well-designed anyways. The library has a Cursor type (which is defined as type Cursor = [XMLNode]), and the operations for looking deeper inside the XML node all have type XMLNode -> [XMLNode] (or, alternately, XMLNode -> Cursor), so they encourage you to chain the operations using >>= (by []'s Monad instance). This is pretty silly in my opinion; the solution here is to make a cleaner library interface, rather than using >>= on list Monad instances as they suggest.

So in summary, I don't think this is a problem with Flow; someone should just make a better XML library interface.

tfausak commented 8 years ago

I feel your pain with xml-conduit. Have you tried xml-lens?

sid-kap commented 8 years ago

xml-lens looks better. Thanks!