Тернарный оператор ввода - PullRequest
9 голосов
/ 29 октября 2011

Я реализовал троичный оператор, такой как Java <condition> ? <if true> : <if false>, заменив / на :, поскольку : не является допустимым идентификатором:

case class Ternary[T](val o: Option[T]) {
  def / (f: => T) = o getOrElse f
}

implicit def boolToTernary(cond: Boolean) = new {
  def ? [T](f: => T) = if(cond) Ternary(Some(f)) 
                        else    Ternary[T](None)
}

В целом работает нормально, например,

scala> (1 > 2) ? "hi" / "abc"
res9: java.lang.String = abc

, но падает в следующем случае:

scala> (1 > 2) ? 5 / 6.0
<console>:33: error: type mismatch;
 found   : Double(6.0)
 required: Int
       (1 > 2) ? 5 / 6.0
                     ^

Есть ли какая-нибудь настройка, которую я могу сделать для типов, чтобы заставить это работать как встроенная if (1 > 2) 5 else 6.0? Я гуглил похожие решения и обнаружил, что все реализации демонстрировали одинаковое поведение.

Ответы [ 3 ]

12 голосов
/ 29 октября 2011

Одна вещь, которую вы можете сделать, это изменить ваше определение / на следующее:

def /[U >: T](f: => U) = o getOrElse f

Это не работает как обычный if (где тип вывода будет Double)- вы получаете AnyVal, но это уже улучшение с очень маленькой модификацией вашего кода.

Обновление: Я думаю, что нашел (немного более сложный) способ сделатьон ведет себя больше как нормальный, если (например, в этом случае, выводя Double).Попробуйте этот код:

implicit def boolToTernary(cond: Boolean) = new {
  def ?[T](f: => T) = if (cond) Ternary(Some(f))
  else Ternary[T](None)
}

case class Ternary[T](val o: Option[T]) {
  def /[U, That](f: => U)(implicit conv: BiConverter[T, U, That]): That = o map conv.toThat1 getOrElse (conv toThat2 f)
}

class BiConverter[T, U, That](val toThat1: T => That, val toThat2: U => That)

trait LowPriorityBiConverterImplicits {
  implicit def subtype[A, T <: A, U <: A]: BiConverter[T, U, A] = new BiConverter[T, U, A](identity[T], identity[U])
}

object BiConverter extends LowPriorityBiConverterImplicits {
  implicit def identityConverter[T]: BiConverter[T, T, T] = new BiConverter[T, T, T](identity, identity)
  implicit def firstAsSecond[T, U](implicit conv: T => U): BiConverter[T, U, U] = new BiConverter[T, U, U](conv, identity)
  implicit def secondAsFirst[T, U](implicit conv: U => T): BiConverter[T, U, T] = new BiConverter[T, U, T](identity, conv)
}

Затем (пример кода):

abstract class Fruit
class Apple extends Fruit
class Banana extends Fruit

def main(args: Array[String]) {
  val int = (1 > 2) ? 5 / 6 // Int is inferred
  val fruit = (1 > 2) ? new Apple / new Banana // Fruit is inferred
  val double1 = (1 > 2) ? 5 / 5.5 // Double is inferred
  val double2 = (1 > 2) ? 5.5 / 5 // Double is inferred
}
9 голосов
/ 29 октября 2011

Вот моя версия, потому что мне любопытно.Я бы не стал этим пользоваться ...

Я выбрал операторы с низким приоритетом.^ сначала свяжется, затем |?.Я использую тот факт, что кортежи являются ковариантными, чтобы вывести тип результата.

case class TernClause[T](t: T) {
  def ^[U](u: U) = (t, u)
}
case class Tern(b: Boolean) {
  def |?[U](tuple: (U,U)) = if (b) tuple._1 else tuple._2
}

implicit def toTern(b: Boolean): Tern = Tern(b)
implicit def toTernClause[T](t: T): TernClause[T] = TernClause(t)

(1 > 2) |? "hi" ^ "abc"
// java.lang.String = abc

(1 > 2) |? 5 ^ 6.0
// AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} = 6.0

Другой пример, показывающий, как приоритет оператора работает вместе:

3 > 2 |? 5 - 1 ^ 6.0 + 1
// AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} = 4

Возможно, для обеспечениянеиспользуемая ветвь не оценивается.

Просто пища для размышлений: обратите внимание, что вывод типа делает правильные вещи при вызове функции.Будет ли работать менее гибкий синтаксис?Это действительно намного проще ...

class BooleanEx(b: Boolean) {
  def ?[U](onTrue: => U, onFalse: => U) = if (b) onTrue else onFalse
}

implicit def toBooleanEx(b: Boolean): BooleanEx = new BooleanEx(b)

class A
class B extends A

(1 > 2) ? ("hi", "abc")
//res0: java.lang.String = abc
(1 > 2) ? (5, 6.0)
//res1: Double = 6.0
(1 > 2) ? (5, 6)
//res2: Int = 6
(1 > 2) ? (new A, new B)
//res3: A = B@1e21540

Кроме того, это доступно в scalaz, но они называют его fold:

import scalaz._
import Scalaz._
(1 > 2) fold (5, 6.0)
//res0: Double = 6.0
2 голосов
/ 31 октября 2011

Я поигрался с решением @ Jean-Philippe и сделал несколько дополнений, чтобы разрешить объединение операторов. (Ну, я мог бы просто оставить все как есть и использовать круглые скобки, но где в этом удовольствие?) Вероятно, есть лучший способ сделать это, поэтому предложения по улучшению приветствуются.

implicit def boolToTernary(cond: Boolean) = new {
  // operator name changed
  def |? [T](f: => T) = if (cond) Ternary(Some(f)) else Ternary[T](None)
}

case class Ternary[T](o: Option[T]) {
  def or [U, That](f: => U)      (implicit conv: BiConverter[T, U, That]): That = o map conv.toThat1 getOrElse (conv toThat2 f)
  // overload added
  def or [U, That](t: Ternary[U])(implicit conv: BiConverter[T, U, That]): Ternary[That] = o match {
    case x: Some[_] => Ternary(o map conv.toThat1)
    case None       => Ternary(t.o map conv.toThat2)
  }
}

Я изменил имена операторов: тот, что в классе Ternary, должен иметь более низкий приоритет, но тот, что в неявном def, также должен иметь низкий приоритет, а | имеет самый низкий приоритет, кроме буквенно-цифровых символов.

Также я добавил перегрузку, чтобы мы могли принять еще одно предложение Ternary.

Следовательно

1 > 2 |? 4 or 4 > 6 |? 8.0 or 10  //look, no parentheses!
// Double = 10.0

1 + 1 < 3 |?
  4L or
  4 > 6 |?
    8 or BigInt(10)
// scala.math.BigInt = 4

Круто или как? :) Кому все равно нужен if / else?

...