Тернарный оператор, похожий на?:

94

Я стараюсь избегать таких конструкций:

val result = this.getClass.getSimpleName
if (result.endsWith("$")) result.init else result

Хорошо, в этом примере thenи elseветви просты, но вы можете изображения сложные. Я построил следующее:

object TernaryOp {
  class Ternary[T](t: T) {
    def is[R](bte: BranchThenElse[T,R]) = if (bte.branch(t)) bte.then(t) else bte.elze(t)
  }
  class Branch[T](branch: T => Boolean) {
    def ?[R] (then: T => R) = new BranchThen(branch,then)
  }
  class BranchThen[T,R](val branch: T => Boolean, val then: T => R)
  class Elze[T,R](elze: T => R) {
    def :: (bt: BranchThen[T,R]) = new BranchThenElse(bt.branch,bt.then,elze)
  }
  class BranchThenElse[T,R](val branch: T => Boolean, val then: T => R, val elze: T => R)
  implicit def any2Ternary[T](t: T) = new Ternary(t)
  implicit def fct2Branch[T](branch: T => Boolean) = new Branch(branch)
  implicit def fct2Elze[T,R](elze: T => R) = new Elze(elze)
}

Определив это, я могу заменить приведенный выше простой пример на:

this.getClass.getSimpleName is {s: String => s.endsWith("$")} ? {s: String => s.init} :: {s: String => s}

Но как я могу избавиться от этого s: String =>? Я хочу что-то подобное:

this.getClass.getSimpleName is {_.endsWith("$")} ? {_.init} :: {identity}

Я предполагаю, что компилятору нужны дополнительные материалы для определения типов.

Петер Шмитц
источник
Поскольку у меня на самом деле этого не было в моем ответе - причина, по которой у вас возникли проблемы, заключается в том, что вывод типа лучше всего работает слева направо, но вы связываете свои токены вместе справа налево из-за приоритета оператора. Если вы сделаете все свои утверждения словами (с одинаковым приоритетом) и измените способ группировки, вы получите желаемый вывод. (Т.е. вы бы HasIs, IsWithCondition, ConditionAndTrueCaseклассы , которые будут наращиваться части выражения слева направо.)
Рекс Керр
Я бессознательно предполагал, что тип вывода слева направо, но придерживался приоритета операторов и ассоциативности имен методов, особенно начиная с ?любого другого буквенного символа в качестве первого символа имени метода и символа a :для левой ассоциативности. Поэтому мне нужно переосмыслить новые имена методов, чтобы вывод типов работал слева направо. Благодарность!
Питер Шмитц,

Ответы:

28

Мы можем комбинировать Как определить тернарный оператор в Scala, который сохраняет ведущие токены? с ответом на вопрос: Является ли вариант обертывания значения хорошим шаблоном? получить

scala>   "Hi".getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x}
res0: String = String

scala> List.getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x}
res1: String = List

Это соответствует вашим потребностям?

Рекс Керр
источник
Это очень близко к тому, что я имел в виду. хороший подход. Я подумаю об этом. Причина, по которой я избегал самого первого кода, заключалась в том, чтобы быть более кратким и не иметь временного valдля следующего ifутверждения: Делайте это понятно в одной строке, как если бы вы это имели в виду.
Питер Шмитц,
125

Из блога Lambda Тони Морриса :

Я часто слышу этот вопрос. Да. Вместо c ? p : qэтого написано if(c) p else q.

Это может быть нежелательно. Возможно, вы захотите написать его, используя тот же синтаксис, что и Java. К сожалению, вы не можете. Это потому, что :это недействительный идентификатор. Не бойся |! Вы бы согласились на это?

c ? p | q

Тогда вам понадобится следующий код. Обратите внимание на =>аннотации call-by-name ( ) для аргументов. Эта стратегия оценки требуется, чтобы правильно переписать тернарный оператор Java. Это невозможно сделать в самой Java.

case class Bool(b: Boolean) {   
  def ?[X](t: => X) = new {
    def |(f: => X) = if(b) t else f   
  } 
}

object Bool {   
  implicit def BooleanBool(b: Boolean) = Bool(b) 
}

Вот пример использования нового оператора, который мы только что определили:

object T {   val condition = true

  import Bool._

  // yay!   
  val x = condition ? "yes" | "no"
}

Радоваться, веселиться ;)

Landei
источник
да, я видел это раньше, но разница в том, что у меня есть (оцененное) значение моего первого выражения в качестве аргумента в предложении thenand else.
Питер Шмитц,
5
Я if(c) p else q
выбрал
17

Ответ Рекса Керра, выраженный на базовом языке Scala:

"Hi".getClass.getSimpleName match {
  case x if x.endsWith("$") => x.init
  case x => x
}

хотя я не уверен, какую часть конструкции if – else вы хотите оптимизировать.

Дебильски
источник
очень прямая дорога. иногда забывают о повседневных операторах соответствия / регистра. Я просто придерживался однострочной троичной if then elseидиомы, но это действительно внятный способ решения.
Питер Шмитц
1
Pattern Matching легко масштабируется до более чем двух ветвей.
Рафаэль
0

Поскольку: сам по себе не будет допустимым оператором, если вы не согласны всегда экранировать его с помощью обратных тиков :, вы можете использовать другой символ, например, "|" как в одном из ответов выше. А как насчет Элвиса с бородкой? ::

implicit class Question[T](predicate: => Boolean) {
  def ?(left: => T) = predicate -> left
}
implicit class Colon[R](right: => R) {
  def ::[L <% R](pair: (Boolean, L)): R = if (q._1) q._2 else right
}
val x = (5 % 2 == 0) ? 5 :: 4.5

Конечно, это снова не сработает, если вы значениями являются списками, поскольку они сами имеют :: operator.

Устаман Сангат
источник