具有灵活类型参数和返回值的函数?

发布于 2024-09-08 13:51:02 字数 3022 浏览 6 评论 0原文

我正在尝试编写一个函数,该函数接受某种类型或其任何子类型作为其参数之一,然后返回该类型或其任何子类型的值。

[<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 类型中实现了一个抽象函数,并在子类型中覆盖(覆盖?)它,但这需要向上转换,这是我试图避免的。

这可行吗?我正在研究泛型,但我还不太了解它们的 F# 实现。

编辑 2010.07.08 1730 PST

关于我使用受歧视工会的建议,我以前尝试过。我遇到的问题是,我定义为基本类型成员的任何函数都必须处理联合的每个分支。例如:

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 函数在这里工作得很好,但如果我想要火燃烧,那么我也必须提供疾病,这是没有意义的。

我希望允许用户可能尝试做一些非法的事情,例如尝试Disease.Burn,但我不想必须在整个地方返回一堆选项类型,例如:

member self.Burn () =
    match self with
    | Fire str -> Some(Fire str)
    | _ -> None

我宁愿只留下燃烧功能严格针对火焰传播者,甚至没有为疾病传播者定义。

此外,这也适用于财产;我希望火拥有一些对疾病没有意义的成员,反之亦然。另外,我希望能够使用点表示法访问 Spreader 的强度值,因为无论如何我都会以这种方式访问​​其他成员,并且在已经定义 member.strength 后必须定义 member.strength 似乎是多余的封装在对象中。 (例如|Disease str -> ...)

当然,另一种选择是简单地将Spread和Burn函数与Spreader类型分开,但是我必须(1)为函数提供丑陋的向上转换或泛型代码(正如其他人所描述的),或者(2)对于火灾和疾病具有完全独立的功能,这在传播的情况下会很糟糕,因为我必须将它们命名为 SpreadFire 和 SpreadDisease (因为奇怪的是不允许函数重载外部类型) 。

作为一个 F# 菜鸟,我欢迎所有批评和建议。 :)

编辑 2010.07.09 0845 PST

Jon Harrop:“为什么要使用增强和成员?”

因为我的实际代码中的类型是数学密集型的,所以我' m 在初始化时预先计算某些值并将它们存储为成员。

Jon Harrop:“为什么你希望burn应用于Speader类型,而它只适用于一种类型?”

正如我所写,我希望Burn应用于吊具。我希望它仅适用于火。然而,我在作为 Spreader 成员实现该函数和将其与 Spreader 类型分离之间左右为难。 (不一致的味道。)

Jon Harrop:“你的 Fn 成员的目的是什么?”

抱歉,那只是无关的代码,请忽略它。

Jon Harrop:“为什么使用抽象类而不是接口?”

主要是代码的可读性和效率。我讨厌仅仅为了使用其中一种方法就必须向上转型为接口。

Jon Harrop:“为什么你将 Strength 定义为 float32 的别名?”

代码可读性。

Jon Harrop:“您想要解决的实际具体问题是什么?!”

相关类型的代码共享,同时保持可读性。

有趣的是,您提供的解决方案正是我第一次尝试的解决方案(我在这个 F# 事情上只花了一周左右的时间),但我放弃了它,因为我对必须包装我的基本类型的想法感到不舒服在 DU 中作为无法重载类型定义之外定义的函数的解决方法。我只是非常担心在基本类型方面表现不佳(部分原因是 VS2010 中缺乏 F# 重构)。函数式编程现在是我非常感兴趣的一个主题,我几乎只是在寻找理解。

编辑 2010.07.09 2230 PST

实际上,我开始不喜欢函数式编程(非常同意作者的观点 http://briancarper.net/blog/315/function-programming-hurts-me -- 如果我在一个例子中看到另一个斐波那契或阶乘编码示例F# 教程或教科书,我要刺伤我找到的下一个书呆子)。

我希望这里有人会以乔恩所表现出的那种蔑视(如下)来回击我最新的回应(解释我为什么要做某些事情)。我想向傲慢的精英们学习 FP,那些自以为自己的东西不臭的程序员。

I'm trying to write a function that accepts a certain type or any of its sub-types as one of its arguments, then returns a value of a type or any of its sub-types.

[<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"

Obviously, this doesn't work but you get what I'm trying to do.

At first, I implemented an abstract function in the Spreader type and overrode (overrided?) it in the sub-types, but that required upcasting, which I'm trying to avoid.

Is this doable? I'm looking into generics, but I've not quite got a grasp on their F# implementation.

EDIT 2010.07.08 1730 PST

Regarding the suggestion that I use discriminated unions, I'd tried that before. The problem I ran into was that any function that I defined as a member of the base type had to process every branch of the union. For example:

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"

The Spread function works fine here, but if I want Fire to Burn, then I have to provide for Disease, too, which makes no sense.

I want to allow for possible attempts by the user to do something illegal like trying to Disease.Burn, but I don't want to have to return a bunch of option types all over the place, e.g.:

member self.Burn () =
    match self with
    | Fire str -> Some(Fire str)
    | _ -> None

I'd rather just leave the Burn function strictly to the Fire Spreader, and not even have it defined for the Disease Spreader.

Furthermore, this goes for properties, too; there are some members that I want Fire to have that don't make sense for Disease, and vice versa. Also, I'd like to be able to access a Spreader's strength value using dot notation, since I'm going to have other members that I access that way anyway, and it seems curiously redundant to have to define member.strength after it's already encapsulated in the object. ( e.g. | Disease str -> ...)

Another option, of course, is to simply separate the Spread and Burn functions from the Spreader type, but then I have to either (1) supply ugly upcasting or generics code for the functions (as others have described), or (2) have totally separate functions for Fire and Disease, which would suck in the case of Spread as I'd have to name them SpreadFire and SpreadDisease (since function overloading outside types is curiously not allowed).

As a noob to F#, I welcome all criticisms and suggestions. :)

EDIT 2010.07.09 0845 PST

Jon Harrop: "Why are you using augmentations and members?"

Because the types in my actual code are mathematics-intensive, so I'm precomputing certain values at initialization and storing them as members.

Jon Harrop: "Why do you want burn to apply to Speader types when it is only applicable to one?"

As I wrote, I don't want Burn to apply to Spreader. I want it to apply solely to Fire. I was torn, however, between implementing the function as a member of Spreader, and separating it from the Spreader type. (Inconsistency smell.)

Jon Harrop: "What is the purpose of your Fn member?"

Sorry, that was just extraneous code, please ignore it.

Jon Harrop: "Why did you use an abstract class instead of an interface?"

Code readability and efficiency, mostly. I hate having to upcast to an interface just to use one of its methods.

Jon Harrop: "Why did you define strength as an alias for float32?"

Code readability.

Jon Harrop: "What is the actual concrete problem you are trying to solve?!"

Code sharing for related types, while maintaining readability.

Interestingly, the solution that you offered is exactly the solution that I first tried (I've been at this F# thing for only a week or so), but I discarded it because I was uncomfortable with the idea of having to wrap my fundamental types in a DU as a work-around for not being able to overload functions defined outside type definitions. I'm just really paranoid about getting off on the wrong foot with fundamental types (in part due to the lamentable lack of F# refactoring in VS2010). Functional programming is a subject of great interest to me right now, and I'm pretty much just fishing around for understanding.

EDIT 2010.07.09 2230 PST

Actually, I/m starting not to like functional programming (really agree with the author at http://briancarper.net/blog/315/functional-programming-hurts-me -- if I see one more Fibonacci or factorial coding example in an F# tutorial or textbook, I'm going to stab the next nerd I find).

I was hoping that someone here would shoot back at my latest response (explaining why I am doing certain things) with the sort of scorn that Jon exhibited (below). I want to learn FP from the haughty elites, the coders who think their sh*t doesn't stink.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

赤濁 2024-09-15 13:51:02

您可能需要显式向上转换您的返回值:

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 -> ...

基本上,如果您的功能仅适用于传播者的子集,那么您需要以某种方式分离关注点。要么通过新类型间接(如上所述),要么使用 OOP 并使类的子集实现接口。

You probably need to explicitly upcast your return values:

let spread (spr: Spreader) =
  match spr with
  | :? Fire -> (Fire() :> Spreader)
  | :? Disease -> (Disease() :> Spreader)

This is a very unusual programming style though. What exactly are you trying to do?

EDIT

I still don't understand what you are trying to accomplish. Why are you using augmentations and members? Why do you want burn to apply to Speader types when it is only applicable to one? What is the purpose of your Fn member? Why did you use an abstract class instead of an interface? Why did you define strength as an alias for float32? What is the actual concrete problem you are trying to solve?!

What about this:

type fire = ...

let burn fire = ...

type spreader = Disease | Fire of fire

let spread = function
  | Disease -> ...
  | Fire fire -> ...

Basically, if you have functions that only apply to a subset of spreaders then you need to separate concerns somehow. Either by indirecting through new types (as above) or by using OOP and making the subset of classes implement an interface.

郁金香雨 2024-09-15 13:51:02

在 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

无论如何,我同意 Brian 的观点,这似乎是一个场景,其中受歧视的联合将是更好的选择。如果您能更详细地描述您的问题,那就太好了。


与 Jon 和 Brian 的 spread2 的解决方案不同,上面的版本返回与作为参数获得的子类型相同的子类型,这意味着以下内容应该有效:

let fire = new Fire()
let fire2 = foo fire       // 'fire2' has type 'Fire'
fire2.FireSpecificThing()

In F#, people don't use implementation inheritance (base classes) that often, so it would be useful to know what are you trying to do (e.g. more realistic example would be great).

If I understand you correctly, the function spread should take a subtype of the type Spreader and return a value of the same type. You can write the signature of the function like this:

let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
  // some implementation here

The type parameter 'T will be some inherited class of Spreader and as the type signature shows, the function foo returns the same type as it takes as the argument.

The implementation is a bit ugly, because you need to add a lot of dynamic type casts:

let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
  match box arg with 
  | :? Fire -> (box (Fire())) :?> 'T
  | :? Disease -> (box (Disease())) :?> 'T

Anyway, I agree with Brian that this seems like a scenario where discriminated unions would be a better choice. It would be great if you described your problem in some more details.


Unlike the solution from Jon and spread2 from Brian, the version above returns the same subtype as the one it gets as an argument, which means that the following should work:

let fire = new Fire()
let fire2 = foo fire       // 'fire2' has type 'Fire'
fire2.FireSpecificThing()
你另情深 2024-09-15 13:51:02

您可以执行下面的 spread1spread2 操作,但我想知道您是否想使用有区别的联合而不是类层次结构。

[<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),如果您认为需要使用它们,通常会产生代码味道。

You can do either of spread1 or spread2 below, but I wonder if you want to use discriminated unions rather than a class hierarchy.

[<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)

EDIT

After your update, it sounds like you do want a class hierarchy, and you would just do things exactly like you'd do them in e.g. C#. So I'd do that. BTW, this

let spread (spr:Spreader) : Spreader = 
    match spr with 
    | :? Fire -> upcast Fire () 
    | :? Disease -> upcast Disease () 
    | _ -> failwith "I don't get it" 

works fine, and is close to your original. I'd make Spread an abstract method on the base class, though.

As an aside, you almost never need "flexible types" (#type) in F#, it is often a code smell if you think you need to use them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文