Рубрики
Без рубрики

Реализация троичного оператора в Scala

Фото Магды Элерс из Pixels Scala не имеет традиционного троичного оператора из Java… Помеченный scala, java, функциональный.

Фото автора Magda Ehlers from Pexels

В Scala нет традиционного троичного оператора из Java

// java
var x = condition ? ifTrue : ifFalse

Вместо этого троичные выражения могут быть определены с помощью ifelse выражений, которые – в отличие от Java – возвращают значение

// scala
var x = if (condition) ifTrue else ifFalse

(Весь код с этого момента является кодом Scala.)

Но это немного многословно. Было бы неплохо, если бы мы могли каким-то образом воссоздать простой ?: обозначения в Scala. Можно ли это сделать? Давай попробуем.

Во-первых, нам нужно подумать о том, что это на самом деле делает. По сути, троичный оператор определяет функцию, которая принимает три аргумента в качестве параметров – логическое значение условие и два параметра по имени , по одному для каждого возможного значения условия .

Наивной реализацией может быть функция с сигнатурой, подобной

def myTernary (condition: Boolean, ifTrue: => Any, ifFalse => Any)

Хотя правильная функциональность может быть реализована, для подписи требуется, чтобы condition , ifTrue и ifFalse передавались в качестве аргументов некоторому методу, где то, что мы действительно хотим, – это condition , за которым следует/| ? , , за которым следует ifTrue и т.д.

Вместо этого мы можем определить метод с именем ? в классе Допустимый и обеспечить неявное преобразование из Логическое значение для Включения , любить

object Implicits {
  implicit class Ternable (condition: Boolean) {
    def ? (ifTrue: => Any, ifFalse: => Any): Any = {
      if (condition) ifTrue else ifFalse
    }
  }
}

Это немного приближает нас, так как теперь мы можем писать код, подобный

import Implicits._

(3 > 2).?("fine", "uh...") // val res0: Any = fine
(3 < 2).?("what", "yeah")  // val res1: Any = yeah

Мы не можем отказаться от . и написать

(3 < 2) ? ("what", "yeah")

…хотя, потому что этот синтаксический сахар работает только тогда, когда функция (в данном случае ? ) принимает один аргумент. На это нужно два.

Мы также хотим добавить символ : между символом if True и если False . Правила ассоциативности Scala говорят, что любые операторы, оканчивающиеся на : , являются правоассоциативными , что означает, что аргумент в правой части :ifFalse — – это тот, для которого должен быть определен оператор : .

С тех пор как если False имеет тип Любой , нам нужно другое неявное преобразование, чтобы добавить метод |/: к Любой тип , но как должна выглядеть сигнатура метода?

Потому что ? имеет более высокий приоритет чем : , первая часть выражения будет вычислена первой

var x = (condition ? ifTrue) : ifFalse

Таким образом, : может принимать один аргумент… но каким должен быть тип этого аргумента? Если верно мог бы оценить, чтобы Любой вид значения, так как же мы можем сигнализировать об этом (1) условии был истинным , Если верно был оценен, и мы должны вернуть это значение по сравнению с (2) условие было ложным , Если верно не был оценен, и нам нужно оценить Если не так

Один из способов – изменить сигнатуру метода ? . Мы можем заставить его вернуть Option[Any] — a Некоторые в случае (1) и a Нет в случае (2)

object Implicits {
  implicit class Ternable (condition: Boolean) {
    def ? (ifTrue: => Any): Option[Any] = {
      if (condition) Some(ifTrue) else None
    }
  }
}

Потому что мы теперь сократили разнообразие из ? от 2 до 1 мы также можем использовать синтаксический сахар, который позволяет нам отбросить . () обозначения для вызовов методов

import Implicits._

(3 > 2) ? "hey" // val res0: Option[Any] = Some(hey)
(3 < 2) ? "hey" // val res1: Option[Any] = None

Это означает, что наш метод : должен принимать Option[Any] в качестве типа аргумента

object Implicits {

  ...

  implicit class Colonable (ifFalse: => Any) {
    def : (intermediate: Option[Any]): Any =
      intermediate match {
        case Some(ifTrue) => ifTrue
        case None => ifFalse
      }
  }
}

Это будет прекрасно работать… если : не были частью синтаксиса базового языка Scala. Помните, что : используется для определения типа объекта (как в val x: String ), поэтому, если мы попытаемся определить метод, как указано выше, мы получим ошибку компилятора (“ожидаемый идентификатор”).

Поскольку мы хотим определить неявный метод в Любой ( у которого очень мало встроенных методов ), мы можем просто выбрать другой оператор, который выглядит как : — как насчет | ? Это уже означает “или” во многих контекстах, что более или менее соответствует тому, что оно означает здесь. Помните, однако, что нам все еще нужен : в качестве последнего символа в имени метода, чтобы получить правильную ассоциативность

object Implicits {

  ...

  implicit class Colonable (ifFalse: => Any) {
    def |: (intermediate: Option[Any]): Any =
      intermediate match {
        case Some(ifTrue) => ifTrue
        case None => ifFalse
      }
  }
}

Зацени это!

import Implicits._

(3 > 2) ? "true" |: "false" // val res0: Any = true
(3 < 2) ? "true" |: "false" // val res1: Any = false

Это работает! С синтаксисом почти таким же чистым , как в Java. (Никогда не думал, что скажу это с невозмутимым лицом.)

Как мы можем улучшить это? Что ж, возвращаемый тип в настоящее время Любой , который далек от идеала. Можем ли мы вывести более узкий тип из типов if True и Если не так ?

Мы могли бы использовать некоторый класс безумия типа , чтобы попытаться найти самый узкий общий супертип (NCS) ifTrue и если False , но для любой разнородной пары типов значений (“примитивов”) NCS равен AnyVal , что не очень полезно.

Вместо этого более похожим на Scala решением может быть использование Либо введите

object Implicits {
  implicit class Ternable (condition: Boolean) {
    def ? [T](ifTrue: => T): Option[T] = {
      if (condition) Some(ifTrue) else None
    }
  }
  implicit class Colonable [T, F](ifFalse: => F) {
    def |: (intermediate: Option[T]): Either[T, F] =
      intermediate match {
        case Some(ifTrue) => Left(ifTrue)
        case None => Right(ifFalse)
      }
  }
}

import Implicits._

((3 > 2) ? "true" |: 42) match {
  case Left(v) => s"$v is a ${v.getClass}"
  case Right(v) => s"$v is a ${v.getClass}"
}

// prints: true is a class java.lang.String

((3 < 2) ? "true" |: false) match {
  case Left(v) => s"$v is a ${v.getClass}"
  case Right(v) => s"$v is a ${v.getClass}"
}

// prints: false is a boolean

Итак, вот оно у вас! Довольно близкое приближение троичного оператора в Scala, который поддерживает как можно больше информации о типе с минимальным шумом.

Дайте мне знать, что вы думаете в комментариях!

Оригинал: “https://dev.to/awwsmm/implementing-a-ternary-operator-in-scala-4lk3”