One of the many good things about F# is that it seamlessly integrates with the .NET ecosystem, right? Very handy in an enterprise environment where in order to get anything done you have to use in-house nugets and tediously building a C# app for something you can do in about 30 LoC in F# just doesn't make sense...

... And then you run into the one fucking namespace in the whole ecosystem that just DOES. NOT. WORK. with F#. What the actual fuck M$?!

In all other cases Func<T',Task> in C# translates into T' -> Task in F#, but not here. "Oh, you're trying to give me Func<T',Task> -> Task? Can't do". Fuck that.

  • 2
    Can you lift the Func into the thing you're trying to call?
  • 1
    @SortOfTested that did not occur to me. Got to try. Thanks for the tip!
  • 0
    @SortOfTested haven't tried yet (since at home and work machine at office), but... I just would really like to know why Microsoft.Azure.ServiceBus.SubscriptionClient.RegisterMessageHandler(Func<Message,CancellationToken,Task>, MessageHandlerOptions) gets the function signature Message -> CancellationToken -> Task in F#, but Microsoft.ServiceBus.Messaging.SubscriptionClient.OnMessageAsync(Func<BrokeredMessage,Task>, OnMessageOptions) keeps the bloody Func<BrokeredMessage,Task> signature and insist on adding the -> Task at the end? It just doesn't really make sense to me...

    For the record, I was using Microsoft.Azure.ServiceBus first, but due to restrictions imposed upon me by the bloody enterprise in-house libs I needed to change for Microsoft.ServiceBus.Messaging, as the bleeding muthafucka needs the BrokeredMessage while Message is no good. Argh.

    ... basically only because I need the (Brokered)Message class extension method GetBody<T>() w/o XmlSerializer...
  • 1
    Edit: on revision, it makes sense

    The second doesn't require a discretionary bifunctor, so that may be why. Generally point free is valuable when you have an arity 2 or greater. Func<T,R> technically only has arity 1, even thought it has typographical arity 2. Another way to say it is there's little point in composing unary function arguments.
  • 0
    @SortOfTested while I kind of understand what you are saying, somehow I don't get it. Would you mind opening it up for me a bit more? 😅
  • 1
    Yeah, no doubt, I love this stuff.

    Consider the Func arg signature on Microsoft.Azure.ServiceBus.SubscriptionClient.RegisterMessageHandler:


    As you know, that means it's going to accept any "2-ary" (bifunctor) method or function that satisfies the (Message, CancellationToken) -> Task signature.

    (message, token) => Task;

    In the point-free style in F# we get:

    message -> token -> Task;

    This gives us some cool capabilities like partial argument binding ( strategy-composition, currying), ex:

    let bifunctor = fun x y -> x + y
    let partialBifunctor = bifunctor 1
    let result = partialBifunctor 2 // expect 3
  • 1

    Now consider the second signature:


    F# fn expression would be:

    fun message -> sendMessage(message);

    This is a "1-ary" unary functor signature. Since it only takes a single argument, the function executes when the last arugment is satisfied, and is therefore mechanically equivalent to Func<T,R>, ex:

    let unaryFunctor = fun x -> x * 2;
    let unaryResult = unaryFunctor 2; // expect 4

    Given that, there's no value in coercing the signature.

    A liftable binary signature would be something like:

    Message -> (unit -> Task)

    Obligatory ex:

    let unaryFunctorComposable = fun x -> fun() -> x * 2;
    let unaryUnitComposition = unaryFunctorComposable 2; // (unit -> int)
    let unaryUnitResult = unaryUnitComposition(); // expect 4
  • 1
    @SortOfTested I wish I had you explaining things to me every time I don't seem to quite get the hang of something! Much better than anything my google-fu yielded. Thanks a lot.
  • 0
    Any time. My F# is a bit rusty, but my functionals are strong. It actually took me a hot minute of "will this work in F# like I expect?" to get to the answer, hence the original edit.

    I see signatures of x y => x*y like x -> y -> z, and know there's some degree of eta-expansion in the language. So then I'd try something like this expecting it to work as is:

    let stringFn () = "Test"
    let printString (str: string) = printf "%s" str
    printString stringFn

    And of course it doesn't bc:

    unit -> string is not type string.

    No biggie, just means I have to manually handle the conversion of unit -> string to string.

    let stringFn () = "Test"
    let printString (str: string) = printf "%s" str
    printString (stringFn())

    Languages like Scala take an odd approach that:

    fn = x -> y -> x * y
    is really just sugar for
    def _fn(x,y) -> x*y // shorthand
    fn = (x) -> _fn.apply(x) -> (y) -> fn.apply(y)();

    Which allows it to perform implicit eta reduction on units that result in the correct type.

    Annnnnyways >.<
Add Comment