I am trying to create an additional constructor in F# that does some extra work (i.e. reads a basic csv file) as follows:
type Sheet () =
let rows = new ResizeArray<ResizeArray<String>>()
let mutable width = 0
new(fileName) as this =
Sheet()
then
let lines = System.IO.File.ReadLines fileName
for line in lines do
let cells = line.Split ','
rows.Add(new ResizeArray<String> (cells)) //line 16
if cells.Length > width then width <- cells.Length
but I get the following errors:
Error 1 The namespace or module 'rows' is not defined C:\Users\ga1009\Documents\PhD\cpp\pmi\fsharp\pmi\Csv.fs 16
Error 2 The value or constructor 'width' is not defined C:\Users\ga1009\Documents\PhD\cpp\pmi\fsharp\pmi\Csv.fs 17
Error 3 The value or constructor 'width' is not defined C:\Users\ga1009\Documents\PhD\cpp\pmi\fsharp\pmi\Csv.fs 17
What am I doing wrong?
Python does not support explicit multiple constructors, yet there are some ways using which the multiple constructors can be achieved. If multiple __init__ methods are written for the same class, then the latest one overwrites all the previous constructors.
The technique of having two (or more) constructors in a class is known as constructor overloading. A class can have multiple constructors that differ in the number and/or type of their parameters. It's not, however, possible to have two constructors with the exact same parameters.
There can be multiple constructors in a class. However, the parameter list of the constructors should not be same. This is known as constructor overloading.
To put it simply, you use multiple constructors for convenience (1st example) or to allow completely different initialization methods or different source types (2nd example. Show activity on this post. A class can have multiple constructors, as long as their signature (the parameters they take) are not the same.
As Daniel pointed out, the F# design is that you have one main constructor that typically takes all the arguments needed by the class and performs the initialization. Other constructors can either provide default values for the arguments or can calculate them from some other information.
In your case, I think that the best design would be to pass rows
as constructor argument. Then you can add two additional constructors (one that loads a file and other that provides empty list).
This makes the code a bit simpler, because you do not have to check if the argument is supplied (as in Daniel's version). I also did a few other simplifications (i.e. calculate width
functionally and use sequence comprehensions - if Sheet
does not modify the data, you could also avoid using ResizeArray
):
type Sheet private (rows) =
// The main constructor is 'private' and so users do not see it,
// it takes columns and calculates the maximal column length
let width = rows |> Seq.map Seq.length |> Seq.fold max 0
// The default constructor calls the main one with empty ResizeArray
new() = Sheet(ResizeArray<_>())
// An alternative constructor loads data from the file
new(fileName:string) =
let lines = System.IO.File.ReadLines fileName
Sheet(ResizeArray<_> [ for line in lines -> ResizeArray<_> (line.Split ',') ])
rows
and width
are not in scope. You can make members to get/set them, or (my recommendation) make the constructor with the most args the primary:
type Sheet (fileName) =
let rows = new ResizeArray<ResizeArray<string>>()
let mutable width = 0
do
match fileName with
| null | "" -> ()
| _ ->
let lines = System.IO.File.ReadLines fileName
for line in lines do
let cells = line.Split ','
rows.Add(new ResizeArray<string> (cells)) //line 16
if cells.Length > width then width <- cells.Length
new() = Sheet("")
Generally, secondary constructors are intended to be overloads of the primary constructor, so they're prevented from interacting with class internals (fields). This encourages a single path of initialization (and better design).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With