尝试在 F# 中将主题与 EasyNetQ 一起使用时出现编译错误

Compilation error when trying to use topics with EasyNetQ in F#

我正在尝试使用 F# 在 EasyNetq 中使用 PubSub.Subscribe 方法订阅 RabbitMq 主题。函数 subscribeToAppQueueWithoutTopic 可以编译并工作,但是 subscribeToAppQueueWithTopic 函数根本不会编译。

let subscribeToAppQueueWithTopic (callback : Subscription<AppEnvelope>) =
    bus.PubSub.Subscribe<AppEnvelope>(String.Empty, callback.OnMessageReceived, 
    (fun (x:ISubscriptionConfiguration) -> x.WithTopic("app.queue")), cts.Token)

Error   FS0041  No overloads match for method 'Subscribe'.
Known types of arguments: string * (AppEnvelope -> unit) * (ISubscriptionConfiguration -> ISubscriptionConfiguration) * CancellationToken

Available overloads:
 - (extension) IPubSub.Subscribe<'T>(subscriptionId: string, onMessage: Action<'T>, configure: Action<ISubscriptionConfiguration>, ?cancellationToken: CancellationToken) : ISubscriptionResult // Argument 'configure' doesn't match
 - (extension) IPubSub.Subscribe<'T>(subscriptionId: string, onMessage: Func<'T,CancellationToken,Tasks.Task>, configure: Action<ISubscriptionConfiguration>, ?cancellationToken: CancellationToken) : ISubscriptionResult // Argument 'onMessage' doesn't match

我在此处找到了一个订阅主题的 C# 示例 EasyNetQ subscription tests,它看起来像这样

 bus.PubSub.SubscribeAsync<Message>(
            Guid.NewGuid().ToString(),
            firstTopicMessagesSink.Receive,
            x => x.WithTopic("first"),
            cts.Token

并认为我可以使用 fun (x:ISubscriptionConfiguration) -> x.WithTopic("app.queue") 作为 F# 中的等价物。唉,这不会编译。

这是一个显示问题的示例应用程序

open System
open EasyNetQ
open System.Threading

type Subscription<'T> = { OnMessageReceived: 'T -> unit }

[<Queue("appqueue", ExchangeName = "demopappexchange")>]
type AppEnvelope = { Message : obj }  

[<EntryPoint>]
let main argv =

    let bus = RabbitHutch.CreateBus("host=localhost")

    let cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

    let printMessage message = 
        printfn "%s" message

    let subscription = {  
        OnMessageReceived = fun (envelope: AppEnvelope) -> (envelope.Message.ToString() |> printMessage )
     }
      
    let sendToAppWithTopic message = 
        async {              
                do! bus.PubSub.PublishAsync({AppEnvelope.Message = message}, "app.queue") |> Async.AwaitTask  
                // bus.Dispose()
            } |> Async.Start
  
    let subscribeToAppQueueWithoutTopic (callback : Subscription<AppEnvelope>) =
        printfn "subscribe called"
        bus.PubSub.Subscribe<AppEnvelope>(String.Empty, callback.OnMessageReceived)

    (*  ** Will not compile **
    let subscribeAsyncToAppQueueWithTopic = 
        async {
            do! bus.PubSub.SubscribeAsync<AppEnvelope>(String.Empty, callback.OnMessageReceived, 
                                                       fun (x: ISubscriptionConfiguration) -> x.WithTopic "scanservice.queue")
                                                       |> Async.AwaitTask
    } |> Async.Start
    *)

    // Will not compile
    let subscribeToAppQueueWithTopic (callback : Subscription<AppEnvelope>) =
        bus.PubSub.Subscribe<AppEnvelope>(String.Empty, callback.OnMessageReceived, (fun (x:ISubscriptionConfiguration) -> x.WithTopic("app.queue")), cts.Token)
   
    subscribeToAppQueueWithoutTopic subscription |> ignore
    sendToAppWithTopic "Testing"

    Console.ReadKey() |> ignore
    0  

我对 EasyNetQ 一无所知,但我认为这里的问题是 WithTopic returns 对变异配置的引用,您需要在 F# 中显式忽略它以便产生一个 Action<_>,像这样:

let subscribeToAppQueueWithTopic (callback : Subscription<AppEnvelope>) =
    bus.PubSub.Subscribe<AppEnvelope>(
        String.Empty,
        callback.OnMessageReceived,
        (fun (x:ISubscriptionConfiguration) -> x.WithTopic("app.queue") |> ignore),
        cts.Token)

显然,API 这样做是为了提供流畅的 C# 界面:

/// <summary>
/// Allows publish configuration to be fluently extended without adding overloads
///
/// e.g.
/// x => x.WithTopic("*.brighton").WithPriority(2)
/// </summary>
public interface IPublishConfiguration
{
    /// <summary>
    /// Sets a priority of the message
    /// </summary>
    /// <param name="priority">The priority to set</param>
    /// <returns>Returns a reference to itself</returns>
    IPublishConfiguration WithPriority(byte priority);

    /// <summary>
    /// Sets a topic for the message
    /// </summary>
    /// <param name="topic">The topic to set</param>
    /// <returns>Returns a reference to itself</returns>
    IPublishConfiguration WithTopic(string topic);

从函数式编程的角度来看,这是一种令人困惑的做事方式,但我想这就是 C# 世界中的生活。