package algorithms
import algorithms.util.IOUtils
object Calculator {
/* static abstract class Operation {
* private Function logic; //functional interface
* Operation(Function f) { this.logic = f );
* }
*/
sealed class Operation(val logic : (Int, Int)-> Int)
/* define singleton types 'ADD', 'SUB', 'MUL', 'DIV' - equivalent to inner class definitions in Java */
/* static class ADD extends Operation {
* ADD(){
* super((int x, int y)-> x y);
* }
* }
*/
object ADD: Operation({ x: Int , y: Int -> x y } )
object SUB: Operation({ x: Int , y: Int -> x-y })
object MUL: Operation({ x: Int , y: Int -> x*y })
object DIV: Operation({ x: Int , y: Int -> x/y })
private fun getOperationFromChar(ch : Char): Operation? {
return when(ch){
' ' -> ADD
'-' -> SUB
'*' -> MUL
'/' -> DIV
else -> null
}
}
fun eval(ch: Char, x: Int, y: Int): Int? {
val op : Operation? = getOperationFromChar(ch)
return op?.logic?.invoke(x,y)
}
}
fun main(){
println("Result : ${Calculator.eval(
IOUtils.readChar("Enter desired operation ( ,-,*,/) "),
IOUtils.readInteger("Enter first number"),
IOUtils.readInteger("Enter second number"))}")
}
The above code works fine, however, IntelliJ forces me to make logic in
return op?.logic?.invoke(x,y)
nullable
Although the definition of Operation sealed class Operation(val logic : (Int, Int)-> Int)
has nowhere mentioned that it can be null.
I would image if the definition of the Operation object was sealed class Operation(val logic : ((Int, Int)-> Int)?)
then it would make sense, but it is not so.
What is going on here?
CodePudding user response:
It's because the return value of getOperationFromChar()
is nullable.
It's not your operation function that returns a nullable value. op
itself is already nullable. You defined it yourself with val operation: Operation?
. When you use ?.
calls, the results are always nullable because null
will be the result if there was no object to call the function on.
The input of your getOperationFromChar()
function is a Char. A Char can be any of many thousands of possible values, not just the four that you have in your when
statement. That's why the compiler is enforcing an else
branch. If you want to avoid returning a nullable, you could choose to throw an error if an invalid input is given:
private fun getOperationFromChar(ch : Char): Operation {
return when(ch){
' ' -> ADD
'-' -> SUB
'*' -> MUL
'/' -> DIV
else -> error("Invalid input $ch")
}
}
Then you could define val op: Operation
and it would be able to accept the result of this function as non-nullable.
Sealed classes help avoid the need for an else
branch when the sealed class is the type of the when
's subject. Then the compiler can be sure you have a branch for every possible input. Your function is the opposite case, where your sealed class type is the output, not the input.
By the way, it is more sensible for you to use an enum instead of a sealed class for this case, because none of the children of your sealed class have unique properties or functions.
CodePudding user response:
It is because op
is nullable; if you set type of op
as Operation
you don't need to check the nullability of logic
.
In fact, it checks the nullability of whole op?.logic
to call invoke()
method; that can throw NullPointerException
due to op
nullability.
CodePudding user response:
The safe call operator(?.) returns null if the value to the left is null, otherwise continues to evaluate the expression to the right. for example
val x:Int? = 4
x?.dec()?.inc()?.dec()
x?.let {
it.dec().inc().dec()
}
CodePudding user response:
If you take the chained evaluation apart, it becomes clear:
fun eval(ch: Char, x: Int, y: Int): Int? {
val op: Operation? = getOperationFromChar(ch)
val logic: ((Int, Int) -> Int)? = op?.logic
val retval: Int? = logic?.invoke(x, y)
return retval
}
logic is not typed ((Int, Int) -> Int) but ((Int, Int) -> Int)?, because if op is null, the result of op?.logic will also be null.