Function with flexible type argument and return value?
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.
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.
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()
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.
精彩评论