colinfang
colinfang

Reputation: 21717

How to throw an exception in a constructor?

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

Answers (3)

Konstantin Konstantinov
Konstantin Konstantinov

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

pad
pad

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

Tomas Petricek
Tomas Petricek

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

Related Questions