ChrisCa
ChrisCa

Reputation: 11006

Pass value to second parameter in a pipeline

what's the correct way to pass a value to the second paramater of a function in a pipeline?

e.g.

async {
    let! response =
        someData
        |> JsonConvert.SerializeObject
        |> fun x -> new StringContent(x)
        |> httpClient.PostAsync "/DoSomething"
        |> Async.AwaitTask
}

in the above code PostAsync takes 2 parameters, the url to post to and the content you want to post. Have tried the back pipe too, and parenthesis, but cant quite figure out how to do it

any ideas?

Upvotes: 1

Views: 211

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243041

In F#, you often need to work with .NET APIs that are not designed to work well as functional pipelines. In those cases, you can do various tricks to fit it into an (often very ugly) pipeline, or you can use the fact that F# is multi-paradigm and write the code in a more object-oriented style.

In your example, I would just use more C#-like style, because the code is not very pipeline-friendly:

async {
    let serialized = JsonConvert.SerializeObject(someData)
    let postData = new StringContent(serialized)
    let! response = httpClient.PostAsync("/DoSomething", postData) |> Async.AwaitTask
}

I think there is also a good theoretical reason why this should not be a pipe - typically, pipelines work well if you have some "main entity" that you are transforming through a series of operations. This can often be some generic type such as list<'a> or non-generic type like Chart.

In your example, you start with object, turn it into JSON, then turn that into StringContent, then Task<'T> etc. - in other words, it's not transforming an entity - it's just doing a lot of unrelated things. In those situations, I prefer to use more explicit coding style rather than pipe.

Upvotes: 3

Fyodor Soikin
Fyodor Soikin

Reputation: 80734

PostAsync has non-curried parameters, which cannot be passed in one by one, they have to be passed all at once. This is why you should always have your parameters curried.

But alas, you can't control the definition of PostAsync, because it's a .NET library method, so you have to wrap it one way or another. There are a few options:

Option 1: use a lambda expression:

|> fun body -> httpClient.PostAsync( "/DoSomething", body )

Option 2: declare yourself a function with curried parameters

let postAsync client url body = 
    client.PostAsync(url, body)

...

|> postAsync httpClient "/DoSomething"

This is usually my preferred option: I always wrap .NET APIs in F# form before using them. This is better, because the same wrapper can transform more than just parameters, but also other things, such as error handling or, in your case, async models:

let postAsync client url body = 
    client.PostAsync(url, body) 
    |> Async.AwaitTask

Option 3: go super general and make yourself a function for transforming any functions from non-curried to curried. In other functional languages such function is usually called uncurry:

let uncurry f a b = f (a, b)

...

|> uncurry httpClient.PostAsync "/DoSomething"

One problem with this is that it only works for two parameters. If you have a non-curried function with three parameters, you'd have to create a separate uncurry3 function for it, and so on.

Upvotes: 6

Related Questions