F# pipeline placeholder?
Asked Answered
V

6

6

I have googlet a bit, and I haven't found what I was looking for. As expected. My question is, is it possible to define a F# pipeline placeholder? What I want is something like _ in the following:

let func a b c = 2*a + 3*b + c

2 |> func 5 _ 6

Which would evaluate to 22 (2*5 + 3*2 + 6).

For comparison, check out the magrittr R package: https://github.com/smbache/magrittr

Vapory answered 14/2, 2014 at 12:38 Comment(1)
I do not think this scenario makes sense unless your func took a tuple: func (a,b,c). Consider what the fsi shows you when you type your function val func : a:int -> b:int -> c:int -> int, which means that you must define b in order to get the function which takes c.Fulcher
A
6

This is (unfortunately!) not supported in the F# language - while you can come up with various fancy functions and operators to emulate the behavior, I think it is usually just easier to refactor your code so that the call is outside of the pipeline. Then you can write:

let input = 2
let result = func 5 input 6

The strength of a pipeline is when you have one "main" data structure that is processed through a sequence of steps (like list processed through a sequence of List.xyz functions). In that case, pipeline makes the code nicer and readable.

However, if you have function that takes multiple inputs and no "main" input (last argument that would work with pipelines), then it is actually more readable to use a temporary variable and ordinary function calls.

Aruabea answered 15/2, 2014 at 14:37 Comment(2)
I agree that this feature would be awesome; so it is unfortunate that it cannot be done properly. I also run into situations where it would be fantastic to be able to do a |> b |> c |> d e . |> f, rather than make temporary variables, or helper functions.Homogenesis
But why does this work? I don't think it should work but it does e.g. backwardTest a b is the same as a - b let backwardTest x y = let minus a b = a - b minus <| x <| yImmerse
V
2

I don't think that's possible, but you could simply use a lambda expression, like

2 |> (fun b -> func 5 b 6)
Vitriolic answered 14/2, 2014 at 12:46 Comment(3)
Exactly what I have been doing up until now. But doing so gives quite a bit of clutter, in my opinion.Vapory
What about a simple helper function like let pipe2 f x z y = f x y z and use it like 2|> pipe2 func 5 6?Vitriolic
Hmmm. I suppose that would be a way. But it is not very flexible.:)Vapory
C
1

You could use a new function like that:

let func a b c = 2*a + 3*b + c
let func2 b = func 5 b 6

2 |> func2
Cissie answered 14/2, 2014 at 13:6 Comment(7)
If that could happen automatically, that would be great. But doing it by myself, again and again, is not particularly pretty either.Vapory
Just curios about the context of the parent task and how often you have to do this per day?Cissie
There is no current task. I am not in need of it right now. But it is something I have needed several times. If I have ten |>'s beneath each other - in five of which the value should not be passed into the last parameter - then this small feature would make the code a lot prettier, and a lot easier to read. I don't have an estimate of how many times per day. But I work with F# every day, as part of my work. Hence, it could become very useful, very often.Vapory
For other people it will be harder to read, because one would expect lambda for a single call and a curried function for repeated calls with the same fixed variables. Some "ad hoc magic" could be confusingCissie
Yes, you would, if you are an experienced F# programmer. If it suddenly became part of the standard F# syntax, you would too, at first. But then you would become used to it. One point I have to make is, that the people I work with are not experienced F# programmers.Vapory
I voted this up, but wanted to say that I do not think that this is currying. I think this is defining a new function which calls the old function with specific parameters. Currying happens twice when you call func a b c, because this is not a tuple. The currying is when you call func a and it returns a function which takes b. The function which takes b returns a function which takes c and returns a number.Fulcher
@PhillipScottGivens thanks for such a detailed clarification! I just needed to name this fast, but missed the pointCissie
G
1

Here's a point-free approach:

let func a b c = 2*a + 3*b + c
let func' = func 5 >> (|>) 6
let result = 2 |> func'
// result = 22

I have explained it in details here.

Be aware, however, that someone who would work with your code will not quickly grasp your intent. You may use it for purposes of learning the deeper aspects of the language, but in real-world projects you will probably find a straightforward approach suitable better:

let func' b = func 5 b 6
Gurrola answered 14/2, 2014 at 14:50 Comment(0)
G
0

@Dominic Kexel's right on the money. If the object isn't really the placement of a placeholder in the chain of arguments, which could have been achieved by a lambda function, but changing their order, then it's more a case of flip than pipe.

From the simple two-argument case

let flip f b a = f a b
// val flip : f:('a -> 'b -> 'c) -> b:'b -> a:'a -> 'c

we need to derive a function

let flip23of3 f a c b = f a b c
// val flip23of3 : f:('a -> 'b -> 'c -> 'd) -> a:'a -> c:'c -> b:'b -> 'd

in order to flip the second and third argument. This could have also been written

let flip23of3' f = f >> flip

let func a b c = 2*a + 3*b + c
2 |> flip23of3 func 5 6
// val it : int = 22
Geiss answered 14/2, 2014 at 18:35 Comment(0)
V
-1

I have given it a try myself. The result is not perfect, but it is as close as I have gotten:

let (|.|) (x: 'a -> 'b -> 'c) (y: 'b) = fun (a: 'a) -> x a y

let func (a:string) b (c:int) = 2.*(float a) + b + 5.*(float c)

let foo = func "4" 9. 5
printfn "First: %f" foo

let bar =
    "4"
    |> ((func |.| 9.) |.| 5)

printfn "Second: %f" bar

let baz =
    9.
    |> (func "4" |.| 5)
printfn "Third: %f" baz

The output is, as expected

First: 42.000000
Second: 42.000000
Third: 42.000000
Vapory answered 14/2, 2014 at 13:51 Comment(3)
IMHO, This is way much harder to read than a lambda/curried functions and longer to write. It adds unneeded complexity and required to learn a new operatorCissie
It is much harder to read than a lambda function, I agree. It is however the closest to what I asked for. If I could write as I wrote in my post, that would be the easiest to read. Curried functions are not really part of the picture, since they are applicable in completely different situations, that where I would expect this.Vapory
IMO doing it this way clearly favors the person writing the code rather than the one who has to maintain it. As a general rule, I write for maintainability rather than convenience of the person writing the code.Jointed

© 2022 - 2024 — McMap. All rights reserved.