Skip to content
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

Add a pipe bang (|>!) operator #791

Open
ChechyLevas opened this issue Oct 2, 2019 · 8 comments

Comments

@ChechyLevas
Copy link

commented Oct 2, 2019

Pipe bang

I propose we add a new operator (|>!) that allows us to unwrap a nomadic value and pipe it at the same time.

The existing way of approaching this problem in F# is

compExpr{
    let! someValue = someFunction()
    let someOtherValue = someOtherFunction someValue
    //I no longer need to use someValue for anything
}

or alternatively

compExpr{
    let someOtherValue = someFunction() |> aSpecificMapFunction (someOtherFunction someValue)
}

With the pipe bang operator, it could be rewritten as

compExpr{
    let someOtherValue = someFunction() |>! someOtherFunction someValue
}

Pros and Cons

The advantages of making this adjustment to F# are it makes programming in a computation expression more similar to programming outside of a computation expression. It also enables a shorter syntax.

The disadvantages of making this adjustment to F# are ... I can't think of any.

Extra information

Estimated cost : I can't see that this would be a particularly difficult feature to implement.

Affidavit (please submit!)

Please tick this by placing a cross in the box:

Please tick all that apply:

  • This is not a breaking change to the F# language design
@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 2, 2019

Barring some careful application of SRTP (perhaps constraint to require the Bind member?), there wouldn't be a way to do this in a general way without a fundamental adjustment to the F# type system.

@ChechyLevas

This comment has been minimized.

Copy link
Author

commented Oct 3, 2019

Since it is already within a computation expression, there is already a constraint to require the Bind member. Can the same mechanism that is used for let!, not be used for |>!? If so, it would be a fairly small change to enable this syntactic sugar, no?

@ChechyLevas

This comment has been minimized.

Copy link
Author

commented Oct 3, 2019

@cartermp - forgot to include you in my response, so making up for it here.

@cartermp

This comment has been minimized.

Copy link
Member

commented Oct 3, 2019

@ChechyLevas The same mechanism could not be used, since an operator is not the same as the let! keyword from the compiler's perspective.

Another alternative would be to define an operator here, but intercept it in the compiler and do something very different with it. So much like nameof and raise it wouldn't be a "real" operator, but a specialized component that looks like an operator, and instead of operating on a 'T, it would operate on some Foo<'T> with a builder that defines the appropriate builder members. That would probably be a medium-cost feature.

@ChechyLevas

This comment has been minimized.

Copy link
Author

commented Oct 4, 2019

Perhaps there is a downside to this proposal. We would need 1 operator |>! to behave like a generic map, and another operator, possibly |>!!, to behave like a generic bind. This could be argued to contribute to operator soup.

@abelbraaksma

This comment has been minimized.

Copy link

commented Oct 4, 2019

It is currently possible to define this for any specific computation expression. As @cartermp already mentions, with some SRTP magic it is perhaps possible to make this generic, given present day language features.

Instead of making this particular change for only one operator, we should try to find a way to make it easier to write such operators that should work seamlessly inside CE's. But that immediately raises the questoin: CE's were invented to make functional programming look more imperative. Now we would be trying to get functional features back in? I'm not saying it is a bad idea, but we should think it through and I hope we can come up with something that tackles the generic case (i.e., make it easier to write |>!, <|!, ||>!, <||! and <<! etc) instead of adding more functions to the surface area.

PS, a minor detail, but in the OP, @ChechyLevas wrote:

let someOtherValue = someFunction() |>! someOtherFunction someValue

I think you meant:

let someOtherValue = someFunction() |>! someOtherFunction

Which by itself makes one wonder: |> typically curries. But currying in CE's might be hard to get right when it needs to interact with map and bind and the like. Not sure if |>! should support it.

@gusty

This comment has been minimized.

Copy link

commented Oct 5, 2019

I don't think this is possible to implement fully with SRTPs, because the class to constraint is the builder class which is technically unrelated to the types inside the computation.

In F#+ there is a map operator |>> but it requires a static member in the type class, not the builder class. The builder is a generic one for monadic types. So this is not the same thing.

@amieres

This comment has been minimized.

Copy link

commented Oct 10, 2019

Personally, whenever I create a monadic type, I also create a module with operators:

module Operators =
    let inline ( <*> ) f v   = apply f v
    let inline ( |>> ) v f   = map   f v
    let inline ( >>= ) v f   = bind  f v
    let inline ( >-> ) f g v = f v |>> g
    let inline ( >=> ) f g v = f v >>= g

... that then can be used inside or outside a Computation Expression.

Also, I use |>! and >>! as tee operators https://fsharpforfunandprofit.com/rop/ :

let [<Inline>] inline tee f v = f v ; v

/// tee: call a function but return the input value
/// for logging, debugging
/// use: (5 * 8) |!> printfn "value = %d" |> doSomethingElse
let [<Inline>] inline  (|>!) v f   = f v ; v
let [<Inline>] inline  (>>!) g f   = g >> fun v -> f v ; v

I even have a monadic version:

let inline ( |>>!) v f   = map   (tee f                       ) v
let inline ( >>=!) v f   = bind  (fun w -> f w |>> fun () -> w) v

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.