Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stop Option-ality infecting everything

Tags:

types

scala

I'm new to Scala. I'd like to have a class which expresses something like coordinates measured in positive integers. (This is a simplified example; please don't point to the existence of other coordinate classes; assume that I really need a new class.)

My first attempt was to simply put require(x >= 0 && y >= 0) into the class, but members of my team did not like that this would throw exceptions.

My second attempt returns an Option[Coordinate]. So now every time I create such a coordinate, I have 4 lines where I would normally have just one. Worse, the Optionality infects everything I'm building out these coordinates. Now I have an Option[Row], Option[Rectangle], Option[Cube], Option[Report]...

Other people have advised me to fix the problem by making a Natural number type which would always be positive. But now this pushes the issue to creating a Natural out of a regular integer that might be negative.

I'm new to Scala, but it seems to me that Options, Either, and Try make sense if

(a) the operation can fail, because you're doing I/O

(b) there's a reasonable alternative to the absence of something

It seems that neither of these capture what I'm trying to do here, which is really to guard against someone making a mathematical mistake in their coding.

Advice?

like image 508
NeilK Avatar asked Nov 22 '25 16:11

NeilK


1 Answers

it seems to me that Options, Either, and Try make sense if

The classes you've mentioned are each responsible for a certain effect. You're right in describing them in some context where an effect is needed, like doing IO, or interacting with a method which may throw an exception (possibly a Java API) or wanting to describe a chain of operations where one or more may fail.

I think it boils down to how deep down the rabbit hole you want to go. What I mean by that is that the power of Scala is its type system, and that is what you can and should leverage to your advantage. I agree with you that using an Option[Coordinate] doesn't feel natural, it certainly doesn't to me. The question that immediatly arises is "How can I leverage the type system to help me only accept natural numbers?". Well, it seems that other people have had the same concern as you.

For example, there is a library called shapeless which has under its belt a Nat type representing natural numbers. For example, given the following case class:

import shapeless.Nat

case class Coordinate(x: Nat, y: Nat)

If I create it with natural numbers, everything compiles:

def main(args: Array[String]): Unit = {
  val cord = Coordinate(1, 1)
}

But once I give it a non natural number, the compiler complains!

def main(args: Array[String]): Unit = {
  val cord = Coordinate(-1, 1)
}

With:

Error:(13, 28) Expression -1 does not evaluate to a non-negative Int literal
   val cord = Coordinates(-1, 1)

IMO that is the power of the type system which you should take to your advantage. If you and your team are willing to put the effort to encode these types properly, you can give your team and users a very strong guarantee of "if this compiles, it does what you mean it to do".

like image 154
Yuval Itzchakov Avatar answered Nov 25 '25 09:11

Yuval Itzchakov