Operator Ternary Mirip Dengan?:

94

Saya mencoba menghindari konstruksi seperti ini:

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

Oke, dalam contoh ini, cabang thendan elsesederhana, tetapi Anda dapat menggambar yang kompleks. Saya membuat yang berikut ini:

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

Mendefinisikan itu, saya dapat mengganti contoh sederhana di atas dengan:

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

Tapi bagaimana saya bisa menyingkirkan s: String =>? Saya ingin sesuatu seperti itu:

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

Saya kira kompiler membutuhkan barang ekstra untuk menyimpulkan tipe.

Peter Schmitz
sumber
Karena saya sebenarnya tidak memiliki ini dalam jawaban saya - alasan Anda mengalami masalah adalah jenis inferensi berfungsi paling baik dari kiri ke kanan, tetapi Anda mengikat token Anda dari kanan ke kiri karena prioritas operator. Jika Anda membuat semua pernyataan Anda menjadi kata-kata (dengan prioritas yang sama) dan mengubah cara pengelompokan, Anda akan mendapatkan kesimpulan yang Anda inginkan. (Ie Anda akan memiliki HasIs, IsWithCondition, ConditionAndTrueCasekelas yang akan membangun bagian dari ekspresi dari kiri ke kanan.)
Rex Kerr
Saya secara tidak sadar menganggap cara inferensi tipe dari kiri ke kanan, tetapi terjebak dengan prioritas operator dan asosiasi nama metode, terutama dimulai dengan ?sebelum karakter alfanum lainnya sebagai nama metode karakter pertama dan :untuk asosiasi kiri. Jadi saya harus memikirkan kembali nama metode baru agar inferensi tipe bekerja dari kiri ke kanan. Terima kasih!
Peter Schmitz

Jawaban:

28

Kita dapat menggabungkan Bagaimana cara mendefinisikan operator terner di Scala yang mempertahankan token terkemuka? dengan jawaban untuk Apakah Opsi membungkus nilai pola yang baik? mendapatkan

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

Apakah ini cukup untuk kebutuhan Anda?

Rex Kerr
sumber
Itu sangat dekat dengan apa yang ada dalam pikiran saya. pendekatan yang bagus. Aku akan memikirkannya. Alasan saya untuk menghindari kode pertama adalah agar lebih ringkas karena tidak memiliki sementara valuntuk pernyataan berikut if: Lakukan dengan jelas dalam satu baris, seperti yang ada dalam pikiran.
Peter Schmitz
125

Dari Blog Lambda Tony Morris :

Saya sering mendengar pertanyaan ini. Ya, itu benar. Alih-alih c ? p : q, itu tertulis if(c) p else q.

Ini mungkin tidak disukai. Mungkin Anda ingin menulisnya menggunakan sintaks yang sama dengan Java. Sayangnya, Anda tidak bisa. Ini karena :bukan pengenal yang valid. Jangan takut |! Apakah Anda akan menerima ini?

c ? p | q

Maka Anda membutuhkan kode berikut. Perhatikan =>anotasi panggilan-oleh-nama ( ) pada argumen. Strategi evaluasi ini diperlukan untuk menulis ulang operator terner Java dengan benar. Ini tidak dapat dilakukan di Java sendiri.

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

Berikut adalah contoh penggunaan operator baru yang baru saja kita tentukan:

object T {   val condition = true

  import Bool._

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

Selamat bersenang-senang ;)

Landei
sumber
ya, saya telah melihat ini sebelumnya, tetapi perbedaannya adalah, bahwa saya memiliki nilai (dievaluasi) dari ekspresi pertama saya sebagai argumen di klausa thendan else.
Peter Schmitz
5
Saya mengambil if(c) p else qpendekatan ... kurangnya kawat gigi membuat saya sedikit tidak nyaman tetapi itu hanya gaya
rjohnston
17

Jawaban Rex Kerr diekspresikan dalam Scala dasar:

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

meskipun saya tidak yakin bagian mana dari konstruksi if – else yang ingin Anda optimalkan.

Debilski
sumber
cara yang sangat lurus. terkadang orang lupa tentang pernyataan pertandingan / kasus penggunaan sehari-hari. Saya hanya berpegang pada if then elseidiom terner satu baris , tetapi ini memang cara yang dapat dipahami untuk menyelesaikannya.
Peter Schmitz
1
Pola Pencocokan Pola berskala dengan mudah ke lebih dari dua cabang.
Raphael
0

Karena: dengan sendirinya tidak akan menjadi operator yang valid kecuali Anda baik-baik saja dengan selalu meloloskannya dengan tanda centang belakang :, Anda dapat menggunakan karakter lain, misalnya "|" seperti di salah satu jawaban di atas. Tapi bagaimana dengan elvis dengan jenggot? ::

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

Tentu saja ini lagi tidak akan berfungsi jika nilai Anda adalah daftar, karena mereka memiliki :: operator sendiri.

Ustaman Sangat
sumber