Rohan Maity
11/27/2022, 5:06 PM// Initially I have an interface Expr (just like in Java) and have two classes (Constant
// and BinaryPlus) implement that interface
interface Exp {
fun eval(): Double
}
class Constant(val value:Double): Exp {
override fun eval(): Double = value
}
class BinaryPlus(val lhs: Exp, val rhs: Exp): Exp {
override fun eval(): Double {
return lhs.eval() + rhs.eval()
}
}
/**
* Here, I added the stringify() method (which is a tool or operation) to the interface Expr,
* without touching the interface and classes which are implementing it.
*/
fun Exp.stringify() : String {
return when(this) {
is Constant -> value.toString()
is BinaryPlus -> "${lhs.stringify()} + ${rhs.stringify()}"
else -> ""
}
}
// Here is how, I can use it
fun main() {
println(BinaryPlus(Constant(4.0), Constant(9.0)).stringify())
}
/**
* Now in object oriented languages, adding types is easier
* and functional languages adding tools(or operations) is easier
* but since kotlin can act as functional and object oriented and with my above given example.
* I think extension function solves the expression problem for kotlin.
*/
Ruckus
11/27/2022, 7:33 PMI am excluding the part where in bytecode it creates a static functionThat's a pretty significant exclusion. While extension functions are pretty, that's all they are (syntax sugar). There's no functional difference between what you wrote and
fun stringify(exp: Exp) { ... }
println(stringify(BinaryPlus(...))
So I don't think this would actually qualify as a solution, but I may be reading the definition too narrowly.Rohan Maity
11/28/2022, 5:24 AMFilip Dolník
11/28/2022, 12:01 PMstringify
function. (The problem states that you cannot modify existing code.) Therefore, you are just flipping the expression problem matrix as shown in the article (section about the visitor pattern).
Note that you cannot write additional extensions, for example:
fun FunctionCall.stringify(): String {
return "FunctionCall"
}
Well, technically, you can write it but it will not work as expected in the following case:
fun callStringify(exp: Exp) {
// This will always call the original extension
exp.stringify()
}
The reason is that extension functions in Kotlin are dispatched statically. In Closure solution, the method is dispatched dynamically even though it’s declared outside of the record/class body (like Kotlin extensions).Rohan Maity
11/28/2022, 12:30 PMadding type
side, stay object oriented and if it is on the adding tool or operation
side, use extension functions
WDYT?Rohan Maity
11/28/2022, 12:34 PMThe reason extension functions in kotlin are dispatched statically but in clojure, they are dispatched dynamically
Can you shed more light on this? How does the static vs dynamic affects the solution of expression problem IMO, compiler can still identify statically as well.
Filip Dolník
11/28/2022, 12:45 PMdynamic fun Constant.stringify(): String = value.toString()
dynamic fun BinaryPlus.stringify(): String = "${lhs.stringify()} + ${rhs.stringify()}"
...
Notice that the when
expression is no longer there (and therefore you don’t have to modify it when adding new types). But in reality, the when
expression is still there. It’s just performed at runtime by the dynamic dispatch.Rohan Maity
11/28/2022, 5:29 PMRohan Maity
11/28/2022, 5:31 PMI did not think it through after adding operation, what would happen if I happen to add one more type.
Now I think, kotlin with the help of extension functions can give you the flexibility on which side of matrix you would wanna be.
If you codebase is more on adding type side, stay object oriented and if it is on the adding tool or operation side, use extension functions
Any comments on this?
Dan Fingal-Surma
12/03/2022, 7:27 AMDan Fingal-Surma
12/03/2022, 7:31 AMDan Fingal-Surma
12/03/2022, 7:35 AMRohan Maity
12/14/2022, 3:01 AM