Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to throw an exception in a constructor?

Tags:

f#

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?

like image 613
colinfang Avatar asked Dec 08 '22 18:12

colinfang


2 Answers

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!"
like image 118
Tomas Petricek Avatar answered Dec 11 '22 07:12

Tomas Petricek


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.

like image 38
pad Avatar answered Dec 11 '22 07:12

pad