1

I would like to have a run time check on a Double, without having to scatter the check all over my code. I thought that defining an implicit class would do the job, something on the line:

  implicit class Probability(val x: Double) {
    require(x >= 0.0 && x <= 1.0, "Probs are defined btw 0.0 and 1.0")
  }

The missing part, is then to tell Scala to treat Probability as a Double any time after it's construction. For this I suppose it is necessary to require a double side conversion.

object Foo {

  implicit class Probability(val x: Double) {
    require(x >= 0.0 && x <= 1.0, "Probs are defined btw 0.0 and 1.0")
  }

  implicit def asDouble(e: Probability): Double = e.x

  implicit def asProb(d: Double): Probability = new Probability(d)

  def max_(s: Seq[Double]): Double = {
    s.max
  }

  def max(s: Seq[Probability]): Double = {
    s.max
  }
}

val r1 = Foo.max_(List(2.0, 3.0, 5.0))
val r2 = Foo.max(List[Probability]=(2.0, 3.0, 5.0))

EDIT

This might have done the trick. Not sure what happens under the hood.

trait RangeBound
type Probability = Double with RangeBound
implicit def makeProb(p: Double): Probability = {
  assert (p >= 0.0 && p <= 1.0)
  p.asInstanceOf[Probability]
}

val p = List[Probability](0.1, 0.3, 0.2)
val r = p filter (_ > 0.1)

Because this does not work:

trait RangeBound
type Probability = Double with RangeBound
implicit def makeProb(p: Double): Probability = {
  assert (p >= 0.0 && p <= 1.0)
  p.asInstanceOf[Probability]
}

val p = List[Probability](0.1, 0.3, 0.2)
val r2 = p.max

with the error:

Error:(10, 18) No implicit Ordering defined for Double with A$A288.this.RangeBound.
lazy val r2 = p.max;}
                ^
Community
  • 1
  • 1
NoIdeaHowToFixThis
  • 4,484
  • 2
  • 34
  • 69
  • 1
    As soon as you convert back `Probability` to `Double`, you lose any compile time check so I don't see how your approach would help you in any way. – Régis Jean-Gilles Jun 15 '15 at 13:05
  • Right. The ideal would be to equip `Probability` with inheritance from `Double`? Not sure how to do this. – NoIdeaHowToFixThis Jun 15 '15 at 13:37
  • Actually, looking at your code I believe that your question is misleadingly phrased. What you want is not a compile time check, but rather runtime checks that fail as soon as possible (some kind of design by contract). In this regard, your approach is sound. The only annoyance is that as when applying any `Double` method (such as `*`) on a `Probability`, you'll need up with a `Double` instead of a `Probability`, and will then need to convert it back to a `Probability` (either by a type ascription, ot by assigning it to a variable typed as `Proeprty`) – Régis Jean-Gilles Jun 15 '15 at 14:34
  • Yes, sorry, I realized myself that compile time was not feasible in the way I had originally envisioned so I adjusted the aim of my shoot on the go. I have fixed the question. I still can't get the code running - I am trying to have something like ` type Quantile = Double with RangeBound with Ordered[??]` (see edit) – NoIdeaHowToFixThis Jun 15 '15 at 14:39
  • Try this: `implicit val probabilityOrdering = Ordering.Double.asInstanceOf[Ordering[Probability]]` – Régis Jean-Gilles Jun 15 '15 at 14:45
  • @RégisJean-Gilles: yepp, that fixed it. If you post this as an answer, I'll accept it. – NoIdeaHowToFixThis Jun 15 '15 at 15:54
  • 1
    Given that you actually answered your own question (minus the missing Ordering, which is actually tangential to the main question), I suggest that **you** post the answer. – Régis Jean-Gilles Jun 15 '15 at 16:14

2 Answers2

1

This enabled basic run time checks:

trait RangeBound
type Probability = Double with RangeBound

  implicit def makeProb(p: Double): Probability = {
    assert (p >= 0.0 && p <= 1.0)
    p.asInstanceOf[Probability]
  }

  implicit val probabilityOrdering = Ordering.Double.asInstanceOf[Ordering[Probability]]
NoIdeaHowToFixThis
  • 4,484
  • 2
  • 34
  • 69
0

I don't understand what you want.

But, in case, here a little proposition to you.

  class Probability(val x: Double) extends AnyVal {
    override def toString = s"${x * 100} %"
    def PerCent = x * 100
  }

  object Probability {
    def apply(x: Double) = {
      if (x <= 1.0 && x >= 0.0)
        Some(new Probability(x))
      else
        None
    }
  }

  implicit def asProb(d: Double): Probability = Probability(d).get

The implicit conversion should only be called when a method or value is used, like PerCent in my example :

scala> 0.3
res0: Double = 0.3

scala> 0.3.PerCent
res1: Double = 30.0

scala> 1.5
res2: Double = 1.5

scala> 1.5.PerCent
java.util.NoSuchElementException: None.get
  at scala.None$.get(Option.scala:347)
  at scala.None$.get(Option.scala:345)
  at .asProb(<console>:21)
  ... 43 elided
volia17
  • 938
  • 15
  • 29
  • My goal is the other way around: to define values as `Probability`, have the `0 to 1` constrain being checked and still be able to call methods and functions defined for `Doubles` without having to rewrite code – NoIdeaHowToFixThis Jun 15 '15 at 13:51
  • @NoIdeaHowToFixThis You can either have type safety or you can have the convenience of working with doubles, but not both. I'd use Probability as described in this answer and make your functions accept Probability as an input instead of double. You could add methods on Probability to combine with others as well. – Daenyth Jun 15 '15 at 14:22