通过灵活类型的异构列表 [英] heterogeneous lists through flexible types

查看:105
本文介绍了通过灵活类型的异构列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用灵活类型

type IFilter<'a> = 
    abstract member Filter: 'a -> 'a

type Cap<'a when 'a: comparison> (cap) = 
    interface IFilter<'a> with
        member this.Filter x = 
            if x < cap
            then x
            else cap

type Floor<'a when 'a: comparison> (floor) = 
     interface IFilter<'a> with
        member this.Filter x = 
            if x > floor
            then x
            else floor

type Calculator<'a, 'b when 'b:> IFilter<'a>> (aFilter: 'b, operation: 'a -> 'a) = 
    member this.Calculate x = 
        let y = x |> operation
        aFilter.Filter y

type TowerControl<'a> () = 
    let mutable calculationStack = List.empty
    member this.addCalculation (x: Calculator<'a, #IFilter<'a>> ) =
        let newList = x::calculationStack
        calculationStack <- newList

let floor10 = Floor<int> 10
let calc1 = Calculator<int, Floor<int>> (floor10, ((+) 10))

let cap10 = Cap 10
let calc2 = Calculator (cap10, ((-) 5))

let tower = TowerControl<int> ()
tower.addCalculation calc1
tower.addCalculation calc2

在上面的示例

member this.addCalculation (x: Calculator<'a, #IFiler<'a>> ) =  

产生错误

错误FS0670:此代码不够通用.类型变量'a不能一概而论,因为它会逃避其范围.

error FS0670: This code is not sufficiently generic. The type variable 'a could not be generalized because it would escape its scope.

如果已经发布了类似的问题,我们深表歉意. 谢谢.

Apologies if a similar question has already been posted. Thank you.

推荐答案

没有简单的方法可以做到这一点.看来您确实希望calculationStack具有类型:

There's no easy way to do this. It looks like you really want calculationStack to have type:

(∃('t:>IFilter<'a>).Calculator<'a, 't>) list

,但是F#不提供存在类型.您可以使用双重否定编码" ∃'t.f<'t> = ∀'x.(∀'t.f<'t>->'x)->'x提出以下解决方法:

but F# doesn't provide existential types. You can use the "double-negation encoding" ∃'t.f<'t> = ∀'x.(∀'t.f<'t>->'x)->'x to come up with the following workaround:

// helper type representing ∀'t.Calculator<'t>->'x
type AnyCalc<'x,'a> = abstract Apply<'t when 't :> IFilter<'a>> : Calculator<'a,'t> -> 'x

// type representing ∃('t:>IFilter<'a>).Calculator<'a, 't>
type ExCalc<'a> = abstract Apply : AnyCalc<'x,'a> -> 'x

// packs a particular Calculator<'a,'t> into an ExCalc<'a>
let pack f = { new ExCalc<'a> with member this.Apply(i) = i.Apply f }

// all packing and unpacking hidden here
type TowerControl<'a> () = 
    let mutable calculationStack = List.empty

    // note: type inferred correctly!
    member this.addCalculation x =
        let newList = (pack x)::calculationStack
        calculationStack <- newList

    // added this to show how to unpack the calculations for application
    member this.SequenceCalculations (v:'a) =
        calculationStack |> List.fold (fun v i -> i.Apply { new AnyCalc<_,_> with member this.Apply c = c.Calculate v }) v

// the remaining code is untouched

let floor10 = Floor<int> 10
let calc1 = Calculator<int, Floor<int>> (floor10, ((+) 10))

let cap10 = Cap 10
let calc2 = Calculator (cap10, ((-) 5))

let tower = TowerControl<int> ()
tower.addCalculation calc1
tower.addCalculation calc2

这具有一个很大的优点,即无需修改Calculator<_,_>类型就可以工作,并且语义正是您想要的,但是存在以下缺点:

This has the big advantage that it works without modifying the Calculator<_,_> type, and that the semantics are exactly what you want, but the following disadvantages:

  1. 如果您不熟悉这种对存在性进行编码的方法,那么很难遵循.
  2. 即使您很熟悉,也有很多丑陋的样板(两种帮助程序类型),因为F#也不允许匿名通用资格.也就是说,即使假设F#不直接支持存在类型,如果您可以编写类似以下内容的代码,也将更容易阅读:

  1. It's hard to follow if you're unfamiliar with this way of encoding existentials.
  2. Even if you are familiar, there's a lot of ugly boilerplate (the two helper types) since F# doesn't allow anonymous universal qualification either. That is, even given that F# doesn't directly support existential types, it would be much easier to read if you could write something like:

type ExCalc<'a> = ∀'x.(∀('t:>IFilter<'a>).Calculator<'a,'t>->'x)->'x
let pack (c:Calculator<'a,'t>) : ExCalc<'a> = fun f -> f c

type TowerControl<'a>() =
    ...
    member this.SequenceCalcualtions (v:'a) =
        calculationStack |> List.fold (fun v i -> i (fun c -> c.Calculate v)) v

但是相反,我们必须为助手类型及其单个方法命名.最终,即使对于已经熟悉通用技术的人来说,也很难遵循代码.

But instead we've got to come up with names for both helper types and their single methods. This ends up making the code hard to follow, even for someone already familiar with the general technique.

如果您拥有Calculator<_,_>类的可能性很小,那么可能会有一个更简单的解决方案(它可能还取决于实际的Calcuator >类的方法的签名). ,如果它比您在此处介绍的要复杂的话):引入一个ICalculator<'a>接口,让Calculator<_,_>实现该接口,并为calculationStack列出该接口类型的值.对于人们来说,这将更加简单明了,但只有在您拥有Calculator<_,_>的情况下(或者如果已经可以使用的现有接口可以实现),这是可能的.您甚至可以将接口设为私有,以便只有您的代码才能知道其存在.这样的样子:

On the off chance that you own the Calculator<_,_> class, there's a much simpler solution that might work (it may also depend on the signatures of the methods of the real Calcuator<,> class, if it's more complex than what you've presented here): introduce an ICalculator<'a> interface, have Calculator<_,_> implement that, and make calculationStack a list of values of that interface type. This will be much more straightforward and easier for people to understand, but is only possible if you own Calculator<_,_> (or if there's already an existing interface you can piggy back on). You can even make the interface private, so that only your code is aware of its existence. Here's how that would look:

type private ICalculator<'a> = abstract Calculate : 'a -> 'a

type Calculator<'a, 'b when 'b:> IFilter<'a>> (aFilter: 'b, operation: 'a -> 'a) = 
    member this.Calculate x = 
        let y = x |> operation
        aFilter.Filter y
    interface ICalculator<'a> with
        member this.Calculate x = this.Calculate x

type TowerControl<'a> () = 
    let mutable calculationStack = List.empty
    member this.addCalculation (x: Calculator<'a, #IFilter<'a>> ) =
        let newList = (x :> ICalculator<'a>)::calculationStack
        calculationStack <- newList

    member this.SequenceCalculations (v:'a) =
        calculationStack |> List.fold (fun v c -> c.Calculate v) v

这篇关于通过灵活类型的异构列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆