3

I found the following piece of code in the fantomas library for F#. I am having a hard time understanding this as an F# noob. From what I understand, it's a custom operator that takes 3 arguments, but why would an operator need 3 arguments? And what exactly is happening here?

/// Function composition operator
let internal (+>) (ctx: Context -> Context) (f: _ -> Context) x =
    let y = ctx x

    match y.WriterModel.Mode with
    | ShortExpression infos when
        infos
        |> Seq.exists (fun x -> x.ConfirmedMultiline)
        ->
        y
    | _ -> f y

Here's an example of how fantomas uses this operator in ther CodePrinter module.

    let short =
        genExpr astContext e1
        +> sepSpace
        +> genInfixOperator "=" operatorExpr
        +> sepSpace
        +> genExpr astContext e2
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
su8898
  • 1,703
  • 19
  • 23

2 Answers2

2

Operators behave a lot like function names:

let (++) a b c d =
    a + b + c + d

(++) 1 2 3 4

One difference is that operators can be used infix. An operator with more than 2 arguments allows infix only for the first 2 arguments:

// the following is equal:
let f = (++) 1 2 // like a function name
let f = 1 ++ 2   // with infix
f 50 60

I did not find how fantomas uses the operator you mention, would be curious, in particular since fantomas is a high profile f# project.

citykid
  • 9,916
  • 10
  • 55
  • 91
  • 2
    It's about building up a "final" function via a compositional pipeline. – Phillip Carter Sep 08 '21 at 15:46
  • 1
    fantomas uses this operator a lot in their ```CodePrinter``` module. I've posted an example in the question. – su8898 Sep 08 '21 at 16:37
  • 2
    @citykid: so, can an operator that receives more than 2 arguments be used as infix? and if yes, how? you didn't answer the question – knocte Sep 08 '21 at 17:02
  • i provided an example in the answer above. you can use it to curry the first 2 args using infix. – citykid Sep 08 '21 at 17:21
1

It might be instructive to compare this to the regular function composition operator, >>. The definition for this is:

let (>>) (f : a' -> b') (g : b' -> c') (x : a') =
   g ( f x )

Esentially, it applies f to x, and then applies g to the result.

If we have the following functions:

let plusOne i = i + 1
let timesTwo j = j * 2

And apply it the following way:

let plusOneTimesTwo = plusOne >> timesTwo

What we're really doing is something like this:

let plusOneTimesTwo = (>>) plusOne timesTwo

When you don't supply all of the necessary arguments to a function (in this case, x), what you get is a function that takes the remaining arguments and then returns what the original function would return (this is partial application.) In this case, plusOneTimesTwo's function signature is now x : int -> int.

The example you've listed is essentially the same thing, but it's performing additional logic to determine whether it wants to apply the second function to the result y or to return it as-is.

Alphacat
  • 323
  • 2
  • 8