Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Restrict a parameter based on another type parameter value of the same hierarchy

The question may be a bit confusing, but the intention is this:

I would like to restrict a value to be of another value in the hierarchy of the type paremeter. Given that they are all types, it would be great if a similar code to this would be enforced.

sealed trait Direction
case object Buy     extends Direction
case object Sell    extends Direction

case class Trade[D <: Direction](id: Long, itemId: Long, quantity: Int)

case class Hedge[D <: Direction, ??? -> O is D's OppositeDirection](id: Long, itemId: Long, openingTrade: Trade[D], closingTrade: Trade[O])  

in short:

val trade1 = Trade[Buy](id = 1, itemdId = 10, quantity = 100)
val trade2 = Trade[Sell](id = 2, itemdId = 10, quantity = -100)

val validHedge = Hedge[Buy, Sell](id = 563, itemId = 10, openingTrade= trade1, closingTrade = trade2)

is valid, but i should not be allowed to create a hedge for an incorrect pairing. e.g.:

val invalidHedge = Hedge[Buy, Sell](id = 563, itemId = 10, openingTrade= trade1, closingTrade = trade1)

Any suggestions would be appreciated.

UPDATE:

If at any moment i wish i could accept more than one answer in SO, it would be this time. All the answers are valid and of good quality. I'll choose the one that best solves the problem and the one that produces the most readable error. The chosen answer has the opposite value available for use (which surely will come in handy) and does not need implicits. It also produces one of the clearest error messages. The type inequality is very elegant, however there's some elements of magic in it, in particular, that it needs 2 disambiguations. Removing it from the code stops working, for no obvious reason. The implicits pair is certainly a good solution and it closely resembles the original question, but is not as useful and implicits free as the Opposites.

Thanks all for the great answers and fast responses.

For completion, here are all the codes reworked:

object NotEquality {
  trait =!=[A, B]

  implicit def neq[A, B] : A =!= B = null
  implicit def neqAmbig1[A] : A =!= A = null
  implicit def neqAmbig2[A] : A =!= A = null //Without this duplicated line, the compiler will ignore the restriction and silently fail. Why?
}

object Trades {
  sealed trait Direction
  case object Buy     extends Direction
  case object Sell    extends Direction

  import NotEquality._
  case class Trade[D <: Direction](id: Long, itemId: Long, quantity: Int)
  case class Hedge[D <: Direction, O <: Direction](id: Long, itemId: Long, openingTrade: Trade[D], closingTrade: Trade[O])(implicit ev: D =!= O)
}


object TradesNeq extends App {
  import NotEquality._
  import Trades._

  val trade1 = Trade[Buy.type](1,10,100)
  val trade2 = Trade[Sell.type](2,10,-100)

  val validHedge = Hedge[Buy.type, Sell.type](id = 563, itemId = 10, openingTrade= trade1, closingTrade = trade2)

  println(s"Valid Hedge: ${validHedge}")
//  val invalidHedge = Hedge[Buy.type, Buy.type](563,10,trade1,trade1)
//  println(s"InvalidHedge ${invalidHedge}")
}


object TradesDOImpl extends App {
  sealed trait Direction
  case object Buy     extends Direction
  case object Sell    extends Direction

  class TradeDirection[D <: Direction, Op <: Direction]

  implicit val BuyToSell = new TradeDirection[Buy.type, Sell.type]
  implicit val SellToBuy = new TradeDirection[Sell.type, Buy.type]

  case class Trade[D <: Direction](id: Long, itemId: Long, quantity: Int)

  case class Hedge[D <: Direction, O <: Direction](id: Long, itemId: Long, openingTrade: Trade[D], closingTrade: Trade[O])(implicit d: TradeDirection[D, O])

  val trade1 = Trade[Buy.type](id = 1, itemId = 10, quantity = 100)
  val trade2 = Trade[Sell.type](id = 2, itemId = 10, quantity = -100)

  val validHedge = Hedge[Buy.type, Sell.type](id = 563, itemId = 10, openingTrade= trade1, closingTrade = trade2)
  println(s"Valid Hedge: ${validHedge}")
  val invalidHedge = Hedge[Buy.type, Buy.type](563,10,trade1,trade1)
  println(s"InvalidHedge ${invalidHedge}")
}


object TradesOpposite extends App {
    sealed trait Direction
    abstract class OppositeDirection[D <: Direction](val opposite: D) extends Direction
    abstract class Buy(opposite: Sell) extends OppositeDirection[Sell](opposite)
    case object Buy extends Buy(Sell)
    abstract class Sell(opposite: Buy) extends OppositeDirection[Buy](opposite)
    case object Sell extends Sell(Buy)

    case class Trade[D <: Direction](id: Long, itemId: Long, quantity: Int)

    case class Hedge[D <: Direction, O <: OppositeDirection[D]](id: Long, itemId: Long, openingTrade: Trade[D], closingTrade: Trade[O])

    val trade1 = Trade[Buy](id = 1, itemId = 10, quantity = 100)
    val trade2 = Trade[Sell](id = 2, itemId = 10, quantity = -100)

    val validHedge = Hedge[Buy, Sell](id = 563, itemId = 10, openingTrade= trade1, closingTrade = trade2)
    println(s"Valid Hedge: ${validHedge}")
//    val invalidHedge = Hedge[Buy, Buy](563,10,trade1,trade1)
//    println(s"InvalidHedge ${invalidHedge}")

}
like image 784
fracca Avatar asked Dec 09 '22 07:12

fracca


1 Answers

You can make each Direction object's type parameterized on the type of its opposite Direction:

trait OppositeDirection[D <: Direction] extends Direction
trait Buy extends OppositeDirection[Sell]
case object Buy extends Buy
trait Sell extends OppositeDirection[Buy]
case object Sell extends Sell

and then you can typecheck opposites:

case class Hedge[D <: Direction, O <: OppositeDirection[D]](...)

Even better might be to have your objects be able to get their opposite objects:

abstract class OppositeDirection[D <: Direction](val opposite: D) extends Direction
abstract class Buy(opposite: Sell) extends OppositeDirection[Sell](opposite)
case object Buy extends Buy(Sell)
abstract class Sell(opposite: Buy) extends OppositeDirection[Buy](opposite)
case object Sell extends Sell(Buy)
like image 120
Dan Getz Avatar answered Dec 15 '22 01:12

Dan Getz