Reputation: 21717
Doesn't it make sense if I do
type Point =
struct
val Row: int
val Column: int
new (row, column) = if row >= 0 && column >= 0 then { Row = row; Column = column }
else failwith "Cooridinators must be non-negative!"
// This is not a valid object construction
static member (+) (x: Point, y: Point) = Point (x.Row + y.Row, x.Column + y.Column)
static member (-) (x: Point, y: Point) = Point (x.Row - y.Row, x.Column - y.Column)
static member (*) (x: Point, a) = Point (x.Row * a, x.Column * a)
static member (*) (a, x: Point) = Point (x.Row * a, x.Column * a)
end
If it was a class then maybe I can raise exception during the do
binding, but in a structure there is no do
, what am I supposed to do?
I figured out that it is possible to add another constructor after failwith
to get around this, but it raises another question, how can I call the implicit constructor? Do I have to construct it explicitly first like
new () = { Row = 0; Column = 0}
// Error structs auto supports a default constructor
If i simply do this using the default constructor
new (row, column) = if row >= 0 && column >= 0 then { Row = row; Column = column }
else
failwith "Cooridinators must be non-negative!"
new Point () // error
It appears to me that Point ()
returns a unit rather than Point
?
Upvotes: 4
Views: 729
Reputation: 1403
It is an old question. However, none of the answers mentioned this: I think that idiomatic F# way is not to throw an exception at all! To achieve that you can use a static member tryCreate
, which would return Some Point
if all the conditions are fulfilled and None
if something is wrong. Then you would follow the trail of option types to code your logic instead of dealing with exceptions! I also think that F# record is probably a more idiomatic way as well, unless you explicitly need struct for some very specific purpose.
Upvotes: 1
Reputation: 41290
You have another alternative to throw exceptions in initialization section:
new (row, column) =
{ Row = ((if row < 0 || column < 0 then failwith "Coordinators must be non-negative!"); row);
Column = column }
Remember that validating struct constructors is not always a good idea. As you found out, you cannot control initialization of default constructors. If it is a class, you can make sure all constructors are validated in the ways you want.
Upvotes: 4
Reputation: 243041
I think the F# compiler complains because constructors should always have a structure:
new (
patterns) =
expressions{
initialization} [then
expressions]
So, the part that initializes the fields cannot be nested under if
or inside any other expression. You can throw exceptions either before the initialization or after (if you add then
keyword). (This matters for classes with inheritance, but I don't think it makes any difference for structs.)
So, one way to write the code is to write:
type Point =
struct
val Row: int
val Column: int
new (row, column) =
if row < 0 || column < 0 then failwith "Cooridinators must be non-negative!"
{ Row = row; Column = column }
// (Static members omitted)
end
Note that I had to negate the condition, because you need to specify the case when you want to throw the exception (rather than saying when you can construct the object). The other alternative would be:
new (row, column) =
{ Row = row; Column = column }
then if row < 0 || column < 0 then failwith "Cooridinators must be non-negative!"
Upvotes: 9