Operador ternário semelhante a?:

94

Estou tentando evitar construções como esta:

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

Ok, neste exemplo o ramo thene elsesão simples, mas você pode imaginar os complexos. Eu construí o seguinte:

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)
}

Definido isso, posso substituir o exemplo simples acima por:

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

Mas como posso me livrar do s: String =>? Eu quero algo assim:

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

Acho que o compilador precisa de material extra para inferir tipos.

Peter Schmitz
fonte
Como eu realmente não tinha isso em minha resposta - o motivo de você estar tendo problemas é que a inferência de tipo funciona melhor da esquerda para a direita, mas você está vinculando seus tokens da direita para a esquerda por causa da precedência do operador. Se você fizer todas as suas declarações palavras (com a mesma precedência) e mudar a maneira como as coisas se agrupam, você obterá a inferência que deseja. (Ou seja, você teria HasIs, IsWithCondition, ConditionAndTrueCaseclasses que iria construir partes da expressão da esquerda para a direita.)
Rex Kerr
Inconscientemente, presumi a forma de inferência de tipo da esquerda para a direita, mas me prendi à precedência de operador e associatividade de nomes de método, especialmente começando com ?antes de qualquer outro caracter de alfanum como um nome de método primeiro char e a :para associatividade à esquerda. Portanto, tenho que repensar os novos nomes de métodos para fazer a inferência de tipo funcionar da esquerda para a direita. obrigado!
Peter Schmitz

Respostas:

28

Podemos combinar Como definir um operador ternário em Scala que preserva os tokens iniciais? com a resposta para Option wrapping a value um bom padrão? para obter

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

Isso é adequado para suas necessidades?

Rex Kerr
fonte
Isso é muito próximo do que tenho em mente. boa abordagem. Eu vou pensar sobre isso. Minha razão para evitar o primeiro código foi ser mais conciso em não ter um temporário valpara uma ifinstrução seguinte : Faça-o inteligível em uma linha, como se tivesse em mente.
Peter Schmitz
125

Do Blog Lambda de Tony Morris :

Eu ouço muito essa pergunta. Sim. Em vez de c ? p : q, está escrito if(c) p else q.

Isso pode não ser preferível. Talvez você queira escrevê-lo usando a mesma sintaxe do Java. Infelizmente, você não pode. Isso ocorre porque :não é um identificador válido. Não tema, |é! Você se conformaria com isso?

c ? p | q

Então você precisará do seguinte código. Observe as =>anotações call-by-name ( ) nos argumentos. Essa estratégia de avaliação é necessária para reescrever corretamente o operador ternário do Java. Isso não pode ser feito no próprio 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) 
}

Aqui está um exemplo usando o novo operador que acabamos de definir:

object T {   val condition = true

  import Bool._

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

Diverta-se ;)

Landei
fonte
sim, eu já vi isso antes, mas a diferença é que a Tenho o valor (avaliado) de minha primeira expressão como um argumento na cláusula thene else.
Peter Schmitz
5
Eu escolhi a if(c) p else qabordagem ... a falta de aparelho me deixa um pouco desconfortável, mas isso é apenas uma coisa de estilo
rjohnston
17

Resposta de Rex Kerr expressa em Scala básico:

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

embora eu não tenha certeza de qual parte da construção if – else você deseja otimizar.

Debilski
fonte
maneira muito direta. às vezes esquecemos das declarações de correspondência / caso de uso diário. Eu apenas mantive o if then elseidioma ternário de uma linha , mas é realmente uma maneira inteligível de resolver.
Peter Schmitz
1
A correspondência de padrões pode ser facilmente dimensionada para mais de dois ramos.
Raphael de
0

Uma vez que: por si só não será um operador válido a menos que você esteja ok em sempre escapar dele com tiques :, você pode escolher outro caractere, por exemplo, "|" como em uma das respostas acima. Mas que tal Elvis com cavanhaque? ::

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

É claro que isso novamente não funcionará se seus valores forem listas, já que eles próprios têm :: operador.

Ustaman Sangat
fonte