简体   繁体   English

是否可以在 F# 中具有不同泛型参数的类型层次结构上进行递归?

[英]Is it possible to recurse on a type hierarchy with distinct generic parameters in F#?

Let's say I have the following F# code:假设我有以下 F# 代码:

[<AbstractClass>]
type Base<'a>() =
    class end
and Test<'a, 'b>(b: Base<'b>, c: 'b -> 'a) =
    inherit Base<'a>()
    member this.B = b
    member this.C = c

let rec test (b : Base<'a>) : _ =
    match b with
    | :? Test<'a, 'b> as t -> let result = test t.B
                                test (t.C result)
    | _                    -> failwith "Not supported!"

Basically, I would like to recurse on a type (Base<'b> in this case) with a generic parameter that is different to what I am currently using in the current function call (Base<'a> in this case).基本上,我想使用与我当前在当前 function 调用(在本例中为 Base<'a> )中使用的不同的泛型参数对类型(在本例中为 Base<'b> )进行递归。 For example, in the code I am pattern matching on some Base<'a> b, which might be an instance of Test, meaning I am in a function call with Base<'a> currently.例如,在代码中,我在某个 Base<'a> b 上进行模式匹配,这可能是 Test 的一个实例,这意味着我目前正在与 Base<'a> 进行 function 调用。

Pattern matching on Test, I would like to recurse on it's field b of Base<'b>, ie a instance of Base that might have a different generic parameter than 'a. Test 上的模式匹配,我想递归它的 Base<'b> 的字段 b,即 Base 的一个实例,它可能具有与 'a 不同的通用参数。 HOWEVER, when I do this, on the line with (test tB) I get the following warning, which totally destroys what I am trying to do:但是,当我这样做时,与 (test tB) 一致,我收到以下警告,这完全破坏了我正在尝试做的事情:

Warning FS0064: This construct causes code to be less generic than indicated by the type annotations.警告 FS0064:此构造导致代码的通用性低于类型注释所指示的通用性。 The type variable 'a has been constrained to be type 'b.类型变量 'a 已被限制为类型 'b。

My question: Is it possible to get around this constraint/warning somehow in F#?我的问题:是否有可能在 F# 中以某种方式绕过这个约束/警告? I don't understand why the recursive call on tB (let result = test tB) would cause 'a to be same type as 'b.我不明白为什么对 tB (let result = test tB) 的递归调用会导致 'a 与 'b. I would need the two to be able to be different for what I am trying to do.我需要这两者能够在我想做的事情上有所不同。

Thanks.谢谢。

EDIT: Added the actual code giving this issue (at: return NaiveEval con.Eff):编辑:添加了给出这个问题的实际代码(在:return NaiveEval con.Eff):

   type Channel<'a>() =
        let queue = ConcurrentQueue<'a>()
    
        member internal this.Send value =
                queue.Enqueue value
    
        member internal this.Receive =
                let status, value = queue.TryDequeue()
                if status then value else this.Receive

    [<AbstractClass>]
    type Effect<'Result>() =
        class end
    and Input<'Result>(chan : Channel<'Result>, cont : 'Result -> Effect<'Result>) = 
        inherit Effect<'Result>()
        member internal this.Chan = chan
        member internal this.Cont = cont
    and Output<'Result>(value : 'Result, chan : Channel<'Result>, cont : unit -> Effect<'Result>) =
        inherit Effect<'Result>()
        member internal this.Value = value
        member internal this.Chan = chan
        member internal this.Cont = cont
    and Concurrent<'Result, 'Async>(eff: Effect<'Async>, cont: Async<'Async> -> Effect<'Result>) = 
        inherit Effect<'Result>()
        member internal this.Eff = eff
        member internal this.Cont = cont
    and Await<'Result, 'Async>(future: Async<'Async>, cont: 'Async -> Effect<'Result>) =
        inherit Effect<'Result>()
        member internal this.Future = future
        member internal this.Cont = cont
    and Return<'Result>(value : 'Result) =
        inherit Effect<'Result>()
        member internal this.Value = value

    let Send(value, chan, cont) = Output(value, chan, cont)
    let Receive(chan, cont) = Input(chan, cont)
    
    let rec NaiveEval (eff : Effect<'Result>) : 'Result =
        match eff with
        | :? Input<'Result> as input             -> let value = input.Chan.Receive
                                                    NaiveEval <| input.Cont value
        | :? Output<'Result> as output           -> output.Chan.Send output.Value
                                                    NaiveEval <| output.Cont ()
        | :? Concurrent<'Result, 'Async> as con  -> let work = async {
                                                        return NaiveEval con.Eff
                                                    }
                                                    let task = Async.AwaitTask <| Async.StartAsTask work
                                                    NaiveEval <| con.Cont task
        | :? Await<'Result, 'Async> as await     -> let res = Async.RunSynchronously await.Future
                                                    NaiveEval <| await.Cont res
        | :? Return<'Result> as ret              -> ret.Value
        | _                                      -> failwith "Unsupported effect!"

There are a couple of issues here:这里有几个问题:

  • You cannot pattern match against a type that has free type parameters - so :? Test<'a, 'b> as t您不能对具有自由类型参数的类型进行模式匹配 - 所以:? Test<'a, 'b> as t :? Test<'a, 'b> as t will not work - ideally, this would match any Test and set 'a and 'b to the right types, but that's not how pattern matching works (and the type parameters have to be known to the compiler). :? Test<'a, 'b> as t不起作用 - 理想情况下,这将匹配任何Test并将'a'b设置为正确的类型,但这不是模式匹配的工作方式(并且必须知道类型参数编译器)。

  • You are also trying to have a recursive function that calls itself with differnet type parameters, which also is not allowed in F#.您还尝试使用不同类型参数调用自身的递归 function,这在 F# 中也是不允许的。

You can come up with various more or less elegant workarounds.您可以想出各种或多或少优雅的解决方法。 The following is one option:以下是一种选择:

type IOperation = 
  abstract Invoke : Test<'a, 'b> -> unit

and [<AbstractClass>] Base() = 
  abstract Invoke : IOperation -> unit

and [<AbstractClass>] Base<'a>() = 
  inherit Base()

and Test<'a, 'b>(b: Base<'b>, c: 'b -> 'a) =
  inherit Base<'a>()
  member this.B = b
  member this.C = c
  override this.Invoke(op) =
    op.Invoke(this)

let rec test (b : Base) : _ =
    b.Invoke
      ({ new IOperation with 
          member x.Invoke<'b, 'c>(t:Test<'b, 'c>) = 
            test t.B
      })  

It adds a non-generic Base (so that you can write recursive test function) which then has an invoke method that takes IOperation .它添加了一个非泛型Base (以便您可以编写递归test函数),然后它有一个调用IOperation的方法。 This then has a generic Invoke method that gets invoked with Test<'b, 'c> - with the right type parameters - by the implementation in Test .然后它有一个通用的Invoke方法,它被Test<'b, 'c>调用 - 使用正确的类型参数 - 由Test中的实现。

I think this might let you do what you need - but it is hard to say without knowing what specifically are you trying to do!我认为这可能会让你做你需要的事情——但如果不知道你具体要做什么就很难说!

Okay, so after a lot of experimentation, I've finally been able to solve the problem.好的,所以经过大量的实验,我终于能够解决这个问题。 Thanks to @TomasPetricek, I've managed to built a sort of visitor pattern that allows what I am trying to do.感谢@TomasPetricek,我设法建立了一种访问者模式,允许我尝试做的事情。

type EffectVisitor =
    abstract member VisitInput<'Result> : Input<'Result> -> 'Result
    abstract member VisitOutput<'Result> : Output<'Result> -> 'Result
    abstract member VisitConcurrent<'Result, 'Async> : Concurrent<'Result, 'Async> -> 'Result
    abstract member VisitAwait<'Result, 'Async> : Await<'Result, 'Async> -> 'Result
    abstract member VisitReturn<'Result> : Return<'Result> -> 'Result
and [<AbstractClass>] Effect() =
    abstract member Visit : EffectVisitor -> 'Result
and [<AbstractClass>] Effect<'Result>() =
    abstract member Visit<'Result> : EffectVisitor -> 'Result
and Input<'Result>(chan : Channel<'Result>, cont : 'Result -> Effect<'Result>) =
    inherit Effect<'Result>()
    member internal this.Chan = chan
    member internal this.Cont = cont
    override this.Visit<'Result>(input) =
    input.VisitInput<'Result>(this)
and Output<'Result>(value : 'Result, chan : Channel<'Result>, cont : unit -> Effect<'Result>) =
    inherit Effect<'Result>()
    member internal this.Value = value
    member internal this.Chan = chan
    member internal this.Cont = cont
    override this.Visit<'Result>(input) =
    input.VisitOutput<'Result>(this)
and Concurrent<'Result, 'Async>(eff : Effect<'Async>, cont : Async<'Async> -> Effect<'Result>) =
    inherit Effect<'Result>()
    member internal this.Eff = eff
    member internal this.Cont = cont
    override this.Visit<'Result>(con) =
        con.VisitConcurrent<'Result, 'Async>(this)
and Await<'Result, 'Async>(task : Async<'Async>, cont : 'Async -> Effect<'Result>) =
    inherit Effect<'Result>()
    member internal this.Task = task
    member internal this.Cont = cont
    override this.Visit<'Result>(await) =
        await.VisitAwait<'Result, 'Async>(this)
and Return<'Result>(value : 'Result) =
    inherit Effect<'Result>()
    member internal this.Value = value
    override this.Visit<'Result>(input) =
        input.VisitReturn<'Result>(this)

let Send(value, chan, cont) = Output(value, chan, cont)
let Receive(chan, cont) = Input(chan, cont)

let rec NaiveEval<'Result> (eff : Effect<'Result>) : 'Result =
    eff.Visit({
        new EffectVisitor with
            member _.VisitInput<'Result>(input : Input<'Result>) : 'Result =
                let value = input.Chan.Receive
                NaiveEval <| input.Cont value
            member _.VisitOutput<'Result>(output : Output<'Result>) : 'Result =
                output.Chan.Send output.Value
                NaiveEval <| output.Cont ()
            member _.VisitConcurrent(con) =
                let work = async {
                    return NaiveEval con.Eff
                }
                let task = Async.AwaitTask <| Async.StartAsTask work
                NaiveEval <| con.Cont task
            member _.VisitAwait(await) =
                let result = Async.RunSynchronously await.Task
                NaiveEval <| await.Cont result
            member this.VisitReturn<'Result>(ret : Return<'Result>) : 'Result =
                ret.Value
    })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM