简体   繁体   中英

generalize Int -> Int, Int-> String, String -> String, String -> Int

I have 4 methods with one logic and 4 possible type mapping:

  def convertStringToString(in: String): String = ???
  def convertIntToString(in: Int): String = ???
  def convertIntToInt(in: Int): Int = ???
  def convertStringToInt(in: String): Int = ???

I want to generalize input and output type and write logic in one methods. Tried to generelize input parameter:

  def convertToInt[IN](in: IN): Int = in match {
    case x: String if x.forall(_.isDigit) => x.toInt
    case y: Int => y
    case _ => 0
  }
  def convertToString[IN](in: IN): String = convertToInt[IN](in).toString

Could you help me to generalize second:

  def convertToInt[IN, OUT](in: IN): OUT = ???

If you really wanted to, you could have something typeclass-based:

def convert[I, O](in: I)(implicit c: ConversionRule[I, O]): O = {
  if (c.isConvertible(in)) c.convert(in)
  else c.zero
}

trait ConversionRule[I, O] {
  def isConvertible(in: I): Boolean
  def convert(in: I): O
  def zero: O // Could possibly derive the zero from, e.g., a cats Monoid instance where such exists
}

The eagle-eyed may notice that the isConvertible / convert methods match the contract of PartialFunction[I, O] 's isDefinedAt / apply , so may as well just use PartialFunction (and rewrite convert with isDefinedAt / apply )

trait ConversionRule[I, O] extends PartialFunction[I, O] {
  def zero: O
}

zero can be implemented in terms of PartialFunction.applyOrElse , but for the case where zero is constant (which is the case where referential transparency is preserved), this is much faster.

Smart constructors can be defined:

object ConversionRule {
  def apply[I, O](zeroValue: O)(pf: PartialFunction[I, O]): ConversionRule[I, O] =
    new ConversionRule[I, O] {
      override def apply(i: I): O = pf(i)
      override def isDefinedAt(i: I): Boolean = pf.isDefinedAt(i)
      val zero: O = zeroValue
    }

  def totalConversion[I, O](f: I => O): ConversionRule[I, O] =
    new ConversionRule[I, O] {
      override def apply(i: I) = f(i)
      override def isDefinedAt(i: I) = true
      override def zero: O = throw new AssertionError("Should not call since conversion is defined")
    }

  // Might want to put this in a `LowPriorityImplicits` trait which this object extends
  implicit def identityConversion[I]: ConversionRule[I, I] =
    totalConversion(identity)
}

identityConversion means that a convertIntToInt gets automatically generated.

convertStringToInt can then be defined as

implicit val stringToIntConversion = ConversionRule[String, Int](0) {
  case x if x.forAll(_.isDigit) => x.toInt
}

One can define a toString based conversion (basically the non-lawful Show proposed for alleycats):

implicit def genericToString[I]: ConversionRule[I, String] =
  ConversionRule.totalConversionRule(_.toString)

And it should then be possible to define a stringViaInt ConversionRule derivation like:

implicit def stringViaInt[I, O](implicit toInt: ConversionRule[I, Int]): ConversionRule[I, String] =
  convert(convert(in)(toInt))

The only really useful thing this provides is an opt-in to usage of implicit conversions. Whether that's enough of a gain to justify? shrug

(Disclaimer: only the scala compiler in my head has attempted to compile this)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM