I have a discrete union WordContainer that is either a Doc of WordDocument or a Cell of WordTableCell. For the purposes of this specific function each type has the same API in terms of functions. I am using a match expression to determine whether the value is a Doc or a Cell and then have under each case the same code with two different variables.
I have spent some time looking this up and reading documentation to figure out how to remove the duplication. First I thought a type class would be great but those are apparently a foreign concept to F#. Then I thought that maybe an interface could be good, but it seemed like I needed full blown classes for those using actual methods which greatly complicated the type hierarchy that I have right now. Trying to leave the type wrapped and working through that did not work, didn't expect it to but I still tried.
The problem here is that WordTableCell
and WordDocument
offer similar API's, but don't share a base class or interface. This is called "duck typing".
One way to handle this is to factor out the differences by creating your own addParagraph
and addTable
helper functions:
let rec addPartUnified (part: DocumentPart) addParagraph addTable =
match part with
| Par paragraph ->
for run in paragraph do
let wordParagraph : OfficeIMO.Word.WordParagraph = addParagraph run.text
()
| Img image ->
(addParagraph "").AddImage (image.path, 100, 100, OfficeIMO.Word.WrapTextImage.Square) |> ignore
| Tab table ->
let wordTable : OfficeIMO.Word.WordTable = addTable (table.rows, table.cols)
for cell in table.cells do
for row = cell.height - 1 downto 0 do
for col = cell.width - 1 downto 0 do
wordTable.Rows[row+cell.y].Cells[col+cell.x].MergeHorizontally 1
wordTable.Rows[row+cell.y].Cells[col+cell.x].MergeVertically 1
FillWordContainer (Cell wordTable.Rows[cell.y].Cells[cell.x]) cell.content
| Lst listing ->
let wordListing = (addParagraph "").AddList OfficeIMO.Word.WordListStyle.Bulleted
for text in listing do
wordListing.AddItem text |> ignore
You can then call the unified function like this:
and addPart part (master: WordContainer) =
match master with
| Cell cell ->
addPartUnifiied part cell.AddParagraph cell.AddTable
| Doc document ->
addPartUnifiied part document.AddParagraph document.AddTable
This is basically a poor man's typeclass.
Another solution that is slightly more verbose, but perhaps more extensible, is to create a unified interface on the WordContainer
type by defining AddParagraph
and AddTable
members:
type WordContainer =
Doc of OfficeIMO.Word.WordDocument | Cell of OfficeIMO.Word.WordTableCell
member master.AddParagraph(text : string) =
match master with
| Cell cell -> cell.AddParagraph(text)
| Doc document -> document.AddParagraph(text)
member master.AddTable(rows, columns) =
match master with
| Cell cell -> cell.AddTable(rows, columns)
| Doc document -> document.AddTable(rows, columns)
You can then call them like this:
let rec addPart (part: DocumentPart) (master: WordContainer) =
match part with
| Par paragraph ->
for run in paragraph do
let wordParagraph = master.AddParagraph run.text
()
| Img image ->
(master.AddParagraph "").AddImage (image.path, 100, 100, OfficeIMO.Word.WrapTextImage.Square) |> ignore
| Tab table ->
let wordTable = master.AddTable (table.rows, table.cols)
for cell in table.cells do
for row = cell.height - 1 downto 0 do
for col = cell.width - 1 downto 0 do
wordTable.Rows[row+cell.y].Cells[col+cell.x].MergeHorizontally 1
wordTable.Rows[row+cell.y].Cells[col+cell.x].MergeVertically 1
FillWordContainer (Cell wordTable.Rows[cell.y].Cells[cell.x]) cell.content
| Lst listing ->
let wordListing = (master.AddParagraph "").AddList OfficeIMO.Word.WordListStyle.Bulleted
for text in listing do
wordListing.AddItem text |> ignore