我正在尝试编写一个接受某个类型或其任何子类型作为其参数之一的函数,然后返回一个类型或其任何子类型的值。
[<AbstractClass>]
type Spreader () =
abstract Fn : unit -> unit
type Fire () =
inherit Spreader ()
override self.Fn () = ()
type Disease () =
inherit Spreader ()
override self.Fn () = ()
let spread (spr:#Spreader) : #Spreader =
match spr with
| :? Fire -> Fire ()
| :? Disease -> Disease ()
| _ -> failwith "I don't get it"
显然,这不起作用,但你得到了我想要做的事情。
首先,我在Spreader类型中实现了一个抽象函数,并在子类型中覆盖(覆盖?)它,但这需要upcasting,我试图避免。
这可行吗?我正在研究泛型,但我还没有完全掌握它们的F#实现。
编辑2010.07.08 1730太平洋标准时间
关于我使用受歧视的工会的建议,我之前尝试过。我遇到的问题是,我定义为基类型成员的任何函数都必须处理联合的每个分支。例如:
type strength = float32
type Spreader =
| Fire of strength
| Disease of strength
member self.Spread () =
match self with
| Fire str -> Fire str
| Disease str -> Disease str
member self.Burn () =
match self with
| Fire str -> Fire str
| _ -> failwith "Only fire burns"
Spread功能在这里工作正常,但如果我想要Fire to Burn,那么我也必须提供疾病,这是没有意义的。
我想允许用户尝试做非法的尝试,例如尝试使用Disease.Burn,但我不想在整个地方返回一堆选项类型,例如:
member self.Burn () =
match self with
| Fire str -> Some(Fire str)
| _ -> None
我宁愿将Burn功能严格地留给Fire Spreader,甚至没有为疾病分布器定义。
此外,这也适用于属性;有些成员我希望Fire对疾病没有意义,反之亦然。此外,我希望能够使用点表示法访问Spreader的强度值,因为无论如何我将会有其他成员访问,并且在它已经已经定义了member.strength之后似乎很奇怪。封装在对象中。 (例如|疾病str - > ...)
另一个选择当然是简单地将Spread和Burn函数与Spreader类型分开,但是我必须(1)为函数提供丑陋的向上转换或泛型代码(正如其他人所描述的),或者( 2)具有完全独立的Fire和Disease功能,在Spread的情况下会很糟糕,因为我必须将它们命名为SpreadFire和SpreadDisease(因为奇怪的是不允许函数重载外部类型)。
作为F#的菜鸟,我欢迎所有的批评和建议。 :)
编辑2010.07.09 0845太平洋标准时间
Jon Harrop:“你为什么要使用扩充和成员?”
因为我实际代码中的类型是数学密集型的,所以我在初始化时预先计算某些值并将它们存储为成员。
Jon Harrop:“为什么要将刻录应用于Speader类型,只适用于Speader类型?”
正如我写的,我不希望Burn申请Spreader。我希望它仅适用于Fire。然而,在将函数实现为Spreader的成员并将其与Spreader类型分离之间,我被撕裂了。 (不一致的气味。)
Jon Harrop:“你的Fn成员的目的是什么?”
抱歉,这只是无关的代码,请忽略它。
Jon Harrop:“你为什么使用抽象类而不是接口?”
代码可读性和效率,主要是。我讨厌不得不向界面转播只是为了使用它的一种方法。
Jon Harrop:“你为什么要将力量定义为float32的别名?”
代码可读性。
Jon Harrop:“你要解决的实际具体问题是什么?!”
相关类型的代码共享,同时保持可读性。
有趣的是,你提供的解决方案正是我第一次尝试的解决方案(我已经在这个F#的事情只用了一个星期左右),但是我放弃了它,因为我对于必须包装的想法感到不舒服我在DU中的基本类型作为解决方法,因为它无法重载在类型定义之外定义的函数。我真的很偏执,因为在基本类型下错了脚(部分原因是由于VS2010中可悲的缺乏F#重构)。函数式编程是我现在非常感兴趣的主题,而且我只是为了理解而四处寻找。
编辑2010.07.09 2230太平洋标准时间
实际上,我开始不喜欢函数式编程(真的同意http://briancarper.net/blog/315/functional-programming-hurts-me的作者 - 如果我在F#教程或教科书中再看到一个Fibonacci或factorial编码示例,我会去刺伤我发现的下一个书呆子。)
我希望有人在这里回答我最近的回应(解释我为什么要做某些事情),以及Jon展示的那种蔑视(下图)。我想从傲慢的精英那里学习FP,他们认为他们的sh * t并不臭。
答案 0 :(得分:4)
您可能需要明确地转发您的返回值:
let spread (spr: Spreader) =
match spr with
| :? Fire -> (Fire() :> Spreader)
| :? Disease -> (Disease() :> Spreader)
这是一种非常不寻常的编程风格。你究竟想做什么?
修改强>
我仍然不明白你想要完成什么。你为什么使用扩充和成员?当burn
类型仅适用于Speader
时,为什么要将其应用于Fn
类型?您strength
会员的目的是什么?为什么使用抽象类而不是接口?为什么要将float32
定义为type fire = ...
let burn fire = ...
type spreader = Disease | Fire of fire
let spread = function
| Disease -> ...
| Fire fire -> ...
的别名?您试图解决的实际具体问题是什么?!
这个怎么样:
{{1}}
基本上,如果您的功能仅适用于spreaders的子集,那么您需要以某种方式分离关注点。通过间接通过新类型(如上所述)或使用OOP并使类的子集实现接口。
答案 1 :(得分:4)
在F#中,人们不经常使用实现继承(基类),因此知道你想要做什么会很有用(例如,更现实的例子会很棒)。
如果我理解正确,函数spread
应该采用类型Spreader
的子类型并返回相同类型的值。您可以像这样编写函数的签名:
let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
// some implementation here
类型参数'T
将是一些继承的Spreader
类,并且正如类型签名所示,函数foo
返回与参数相同的类型。
实现有点难看,因为你需要添加很多动态类型转换:
let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
match box arg with
| :? Fire -> (box (Fire())) :?> 'T
| :? Disease -> (box (Disease())) :?> 'T
无论如何,我同意布莱恩的观点,这似乎是歧视工会将是更好的选择。如果您在更详细的描述中描述您的问题,那就太好了。
与Jon和来自Brian的spread2
的解决方案不同,上面的版本返回与它作为参数获得的子类型相同的子类型,这意味着以下内容应该有效:
let fire = new Fire()
let fire2 = foo fire // 'fire2' has type 'Fire'
fire2.FireSpecificThing()
答案 2 :(得分:2)
您可以执行下面的spread1
或spread2
,但我想知道您是否要使用受歧视的联合而不是类层次结构。
[<AbstractClass>]
type Spreader () =
abstract Fn : unit -> unit
type Fire () =
inherit Spreader ()
override self.Fn () = ()
type Disease () =
inherit Spreader ()
override self.Fn () = ()
let fire = new Fire()
let disease = new Disease()
let spread1<'t when 't :> Spreader>(spr:'t) : 't =
spr
printfn "%A" (spread1 fire)
printfn "%A" (spread1 disease)
let spread2(spr:Spreader) : Spreader =
match spr with
| :? Fire -> upcast disease
| :? Disease -> upcast fire
| _ -> failwith "hmmm"
printfn "%A" (spread2 fire)
printfn "%A" (spread2 disease)
修改
更新之后,听起来你确实想要一个类层次结构,而你只需要做的就像你在这里做的那样。 C#。所以我会这样做。顺便说一下,这个
let spread (spr:Spreader) : Spreader =
match spr with
| :? Fire -> upcast Fire ()
| :? Disease -> upcast Disease ()
| _ -> failwith "I don't get it"
工作正常,并且接近原件。不过,我会在基类上使Spread
成为一个抽象方法。
顺便说一句,你几乎不需要F#中的“灵活类型”(#type
),如果你认为你需要使用它们,它通常会有代码味道。