Scala: Something like Option (Some, None) but with three states: Some, None, Unknown
Asked Answered
P

7

6

I need to return values, and when someone asks for a value, tell them one of three things:

  1. Here is the value
  2. There is no value
  3. We have no information on this value (unknown)

case 2 is subtly different than case 3. Example:

val radio = car.radioType
  1. we know the value: return the radio type, say "pioneer"
  2. b. there is no value: return None
  3. c. we are missing data about this car, we don't know if it has a radio or not

I thought I might extend scala's None and create an Unknown, but that doesn't seem possible.

suggestions?

thanks!

Update:

Ideally I'd like to be able to write code like this:

car.radioType match { 
   case Unknown => 
   case None => 
   case Some(radioType : RadioType) => 
}
Pneumatometer answered 15/11, 2009 at 16:47 Comment(0)
M
13

Here's a barebones implementation. You probably want to look at the source for the Option class for some of the bells and whistles:

package example

object App extends Application {
  val x: TriOption[String] = TriUnknown

  x match {
    case TriSome(s) => println("found: " + s)
    case TriNone => println("none")
    case TriUnknown => println("unknown")
  }
}

sealed abstract class TriOption[+A]
final case class TriSome[+A](x: A) extends TriOption[A]
final case object TriNone extends TriOption[Nothing]
final case object TriUnknown extends TriOption[Nothing]
Muzz answered 15/11, 2009 at 20:39 Comment(1)
Thanks, that helped a lot. I ended up implementing something similar, with None and Some as a sub-state of Known, via Option: code.alexblack.ca/an-option-class-in-scala-with-three-states-unPneumatometer
D
10

Don't tell anyone I suggested this, but you could always use null for Unknown rather than writing a new class.

car.radioType match { 
   case null => 
   case None => 
   case Some(radioType : RadioType) => 
}
Denham answered 16/11, 2009 at 1:26 Comment(1)
That sounds simple but blasphemous :)Pneumatometer
P
5

You can grab some stuff from Lift: the Box. It has three states, Full, Failure and Empty. Also, Empty and Failure both inherit from EmptyBox.

Paulie answered 15/11, 2009 at 23:27 Comment(0)
W
4

You can use scala.Either. Use Left for the exceptional value, and Right for the expected value which can be an Option in this case:

scala> type Result = Either[String, Option[String]]
defined type alias Result

scala> val hasValue: Result = Right(Some("pioneer"))
hasValue: Result = Right(Some(pioneer))

scala> val noValue: Result = Right(None)
noValue: Result = Right(None)

scala> val unknownValue = Left("unknown")
unknownValue: Left[java.lang.String,Nothing] = Left(unknown)
Winnie answered 15/11, 2009 at 17:3 Comment(1)
Thats a good idea, but seems a bit verbose. Ideally I'd like to write code like this: car.radioType match { case Unknown => case None => case Some(radioType : RadioType) => }Pneumatometer
G
2

You could create your own with the three possibilities. Or as of one your car.radioType types you could have unknown, and then use guards on your case's to handle it. If you roll your own, you should include the Product trait as well. liftweb has the Box type, which is an option close that allows for full, empty and erorr to happen.

Geraint answered 15/11, 2009 at 16:56 Comment(2)
Rolling my own could work - I need it to be used for many types of values, not just car.radioType. I looked at Lift's box, and it looks to me like it only supports two values (Full, Empty): scala-tools.org/scaladocs/liftweb/1.0/net/liftweb/util/Box.htmlPneumatometer
I think you're looking at it wrong. You have a method that returns Box. Box can be one of it's three inherited classes, Full, Failure or Empty if you follow the Option and/or Box as a pattern you could also use flatMap, map, or forEach. You can then use a match statement to determine which value it is.Geraint
L
0

I did something like similar to classify 3 types of lines in given file, a given line maybe, for instance, Float for header line, Long for a line in the middle (row), or String for the trailer line. Also isHeader, isRow and isTrailer can be used to know which one is. Hopefully helps:

sealed abstract class HRT[+H, +R, +T] {
  val isHeader: Boolean
  val isRow: Boolean
  val isTrailer: Boolean
}

final case class Header[+H, +R, +T](h: H) extends HRT[H, R, T] {
  override val isHeader: Boolean = true
  override val isRow: Boolean = false
  override val isTrailer: Boolean = false
}

final case class Row[+H, +R, +T](r: R) extends HRT[H, R, T] {
  override val isHeader: Boolean = false
  override val isRow: Boolean = true
  override val isTrailer: Boolean = false
}

final case class Trailer[+H, +R, +T](t: T) extends HRT[H, R, T] {
  override val isHeader: Boolean = false
  override val isRow: Boolean = false
  override val isTrailer: Boolean = true
}

object Demo {
  def getEntries(): Seq[HRT[Float, Long, String]] =
    List(
      Header(3.14f),
      Row(42),
      Trailer("good bye")
    )

  val entries = getEntries()

  entries.foreach {
    case Header(f) => printf("header: %f\n", f)
    case Row(l) => printf("row: %d\n", l)
    case Trailer(s) => printf("trailer: %s\n", s)
  }
}
Lehrer answered 30/11, 2017 at 4:11 Comment(0)
M
0

With Scala 3 you can use union types like this:

object Unknown
object Empty
type MaybeRadio = RadioType | Empty.type | Unknown.type

car.radioType match
  case Unknown => ???
  case Empty   => ???
  case radioType: RadioType => ???

See https://docs.scala-lang.org/scala3/book/types-union.html

Manley answered 27/11, 2022 at 15:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.