Operator trójskładnikowy podobny do?:

94

Staram się unikać takich konstrukcji:

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

Ok, w tym przykładzie gałęzie theni elsesą proste, ale możesz wyobrazić sobie złożone. Zbudowałem następujące elementy:

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

Zdefiniowałem to, mogę zastąpić powyższy prosty przykład:

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

Ale jak mogę się go pozbyć s: String =>? Chcę czegoś takiego:

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

Myślę, że kompilator potrzebuje dodatkowych rzeczy, aby wywnioskować typy.

Peter Schmitz
źródło
Ponieważ tak naprawdę nie miałem tego w mojej odpowiedzi - powodem, dla którego masz problemy, jest to, że wnioskowanie o typie działa najlepiej od lewej do prawej, ale łączysz swoje tokeny od prawej do lewej ze względu na pierwszeństwo operatorów. Jeśli wszystkie swoje stwierdzenia uczynisz słowami (z tym samym priorytetem) i zmienisz sposób, w jaki rzeczy się grupują, uzyskasz żądany wniosek. (Tj trzeba HasIs, IsWithCondition, ConditionAndTrueCasezajęcia, które budują części wyrazu od lewej do prawej).
Rex Kerr
Nieświadomie zakładałem sposób wnioskowania o typie od lewej do prawej, ale trzymałem się pierwszeństwa operatorów i asocjatywności nazw metod, szczególnie zaczynając od ?przed jakimkolwiek innym znakiem alfanumerycznym jako nazwą metody, pierwszym znakiem i :lewym skojarzeniem. Muszę więc przemyśleć nowe nazwy metod, aby wnioskowanie o typie działało od lewej do prawej. dzięki!
Peter Schmitz,

Odpowiedzi:

28

Możemy łączyć Jak zdefiniować operator trójskładnikowy w Scali, który zachowuje wiodące tokeny? z odpowiedzią na pytanie Czy zawijanie opcji to dobry wzór? dostać

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

Czy jest to odpowiednie dla Twoich potrzeb?

Rex Kerr
źródło
To bardzo zbliżone do tego, co mam na myśli. miłe podejście. Pomyślę o tym. Powodem, dla którego unikałem pierwszego kodu, było bardziej zwięzłe, nie mając tymczasowego valstwierdzenia if: Czy to zrozumiałe w jednej linii, tak jak się ma na myśli.
Peter Schmitz,
125

Z bloga Lambda Tony'ego Morrisa :

Często słyszę to pytanie. Tak. Zamiast c ? p : qtego jest napisane if(c) p else q.

To może nie być preferowane. Być może chciałbyś napisać go przy użyciu tej samej składni co Java. Niestety nie możesz. Dzieje się tak, ponieważ :nie jest to prawidłowy identyfikator. Nie bój się, |jest! Zgodziłbyś się na to?

c ? p | q

Wtedy będziesz potrzebować następującego kodu. Zwróć uwagę na =>adnotacje call-by-name ( ) na argumentach. Ta strategia oceny jest wymagana do prawidłowego przepisania trójskładnikowego operatora Java. Nie można tego zrobić w samej Javie.

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

Oto przykład wykorzystujący nowy operator, który właśnie zdefiniowaliśmy:

object T {   val condition = true

  import Bool._

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

Baw się dobrze ;)

Landei
źródło
tak, widziałem to już wcześniej, ale różnica polega na tym, że a Mam (oszacowaną) wartość mojego pierwszego wyrażenia jako argument w klauzuli theni else.
Peter Schmitz,
5
Wziąłem if(c) p else qpodejście ... brak szelek sprawia, że dotyk niewygodne, ale to jest po prostu coś w stylu
rjohnston
17

Odpowiedź Rexa Kerra wyrażona w podstawowej Scali:

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

chociaż nie jestem pewien, którą część konstrukcji if – else chcesz zoptymalizować.

Dębilski
źródło
bardzo prosta droga. czasami zapomina się o codziennych stwierdzeniach typu match / case. Po prostu trzymałem się jednego wiersza potrójnego if then elseidiomu, ale jest to rzeczywiście zrozumiały sposób rozwiązania.
Peter Schmitz,
1
Dopasowywanie wzorców łatwo skaluje się do więcej niż dwóch gałęzi.
Raphael
0

Ponieważ: samo w sobie nie będzie prawidłowym operatorem, chyba że będziesz w porządku i zawsze będziesz go uciekać za pomocą tylnych tików :, możesz użyć innego znaku, np. "|" jak w jednej z odpowiedzi powyżej. Ale co powiesz na elvisa z bródką? ::

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

Oczywiście to znowu nie zadziała, jeśli wartości są listami, ponieważ same mają operator ::.

Ustaman Sangat
źródło