Receiver functions overcome it by not making you import anything, i.e., you do not introduce new identifiers into the block where you call the function that expects a receiver function (i.e. the block in which you call excelTable
and htmlTable
in my example), but only in the block of code that you pass to that function (in which we then call the row
function).
The whole of the code would then become:
class ExcelTable {
private def addRow(r: ExcelRow): Unit = ???
}
case class ExcelRow(text: String)
object ExcelTable {
class Builder(val et: ExcelTable) {
def row(text: String) = et.addRow(ExcelRow(text))
}
def excelTable(f: this Builder => Unit): ExcelTable = {
val b = new Builder(new ExcelTable)
f(b)
b.et
}
}
class HtmlTable {
private def addRow(r: HtmlRow): Unit = ???
}
case class HtmlRow(text: String)
object HtmlTable {
class Builder(val ht: HtmlTable) {
def row(text: String) = ht.addRow(HtmlRow(text))
}
def htmlTable(f: this Builder => Unit): HtmlTable = {
val b = new Builder(new HtmlTable)
f(b)
b.ht
}
}
object Main {
def main(args: Array[String]): Unit = {
import ExcelTable._
val et = excelTable {
row("I'm in Excel!")
}
import HtmlTable._
val ht = htmlTable {
row("I'm in HTML!")
}
}
}
(This could probably be minimized further, but it’s an extremely small edit distance)