Scala: зачем использовать неявный аргумент функции?



У меня есть следующая функция:



def getIntValue(x: Int)(implicit y: Int ) : Int = {x + y}


Я вижу выше объявление везде. Я понимаю, что делает эта функция. Это функция карринга, которая принимает два аргумента. Если вы опустите второй аргумент, он вызовет неявное определение, которое вместо этого возвращает int. Поэтому я думаю, что это нечто очень похожее на определение значения по умолчанию для аргумента.



implicit val temp = 3

scala> getIntValue(3)
res8: Int = 6


Мне было интересно, каковы преимущества вышеупомянутого заявления?

588   3  

3 ответов:

Вот мой "прагматичный" ответ: вы обычно используете карринг как более "условность", чем что-либо другое значимое. Это очень удобно, когда ваш последний параметр оказывается параметром" call by name " (например: : => Boolean):

def transaction(conn: Connection)(codeToExecuteInTransaction : => Boolean) = {

   conn.startTransaction  // start transaction

   val booleanResult = codeToExecuteInTransaction //invoke the code block they passed in

  //deal with errors and rollback if necessary, or commit
  //return connection to connection pool
}
Это означает, что "у меня есть функция под названием transaction, ее первый параметр-соединение, а второй параметр-блок кода".

Это позволяет нам использовать этот метод так (Используя "я могу использовать фигурную скобку вместо правило скобок"):

transaction(myConn) {

   //code to execute in a transaction
  //the code block's last executable statement must be a Boolean as per the second
  //parameter of the transaction method

}

Если бы вы не выискивали этот метод транзакции, это выглядело бы довольно неестественно:

transaction(myConn, {

   //code block

})

Как насчет implicit? Да, это может показаться очень неоднозначной конструкцией, но вы привыкнете к ней через некоторое время, и хорошая вещь о неявных функциях-это то, что у них есть правила области видимости. Таким образом, это означает, что для производства вы можете определить неявную функцию для получения этого соединения с базой данных из базы данных PROD, но в своем интеграционном тесте вы определите неявная функция, которая будет превосходить версию PROD, и она будет использоваться для получения соединения из базы данных DEV вместо этого для использования в тесте.

В качестве примера, как насчет добавления неявного параметра к методу транзакции?

def transaction(implicit conn: Connection)(codeToExecuteInTransaction : => Boolean) = {

}

Теперь предположим, что у меня есть неявная функция где-то в моей кодовой базе, которая возвращает соединение, например:

def implicit getConnectionFromPool() : Connection = { ...}

Я могу выполнить метод транзакции следующим образом:

transaction {
   //code to execute in transaction
}

И Scala переведет это кому:

transaction(getConnectionFromPool) {
  //code to execute in transaction
}

В общем, Имплициты-это довольно хороший способ не заставлять разработчика предоставлять значение для требуемого параметра, когда этот параметр в 99% случаев будет одинаковым везде, где вы используете функцию. В этом 1% случаев, когда вам нужно другое соединение, вы можете предоставить свое собственное соединение, передав значение вместо того, чтобы позволить Scala выяснить, какая неявная функция предоставляет значение.

В вашем конкретном примере нет никакой практической пользы. На самом деле использование имплицитов для этой задачи только запутает ваш код.

В стандартном случае использовать неявные преобразования, является класс тип рисунка. Я бы сказал, что это единственный случай использования, который практически полезен. Во всех остальных случаях лучше, чтобы вещи были явными.

Вот пример класса typeclass:

// A typeclass
trait Show[a] {
  def show(a: a): String
}

// Some data type
case class Artist(name: String)

// An instance of the `Show` typeclass for that data type
implicit val artistShowInstance =
  new Show[Artist] {
    def show(a: Artist) = a.name
  }

// A function that works for any type `a`, which has an instance of a class `Show`
def showAListOfShowables[a](list: List[a])(implicit showInstance: Show[a]): String =
  list.view.map(showInstance.show).mkString(", ")

// The following code outputs `Beatles, Michael Jackson, Rolling Stones`
val list = List(Artist("Beatles"), Artist("Michael Jackson"), Artist("Rolling Stones"))
println(showAListOfShowables(list))

Этот паттерн происходит от функционального языка программирования под названием Haskell и оказался будьте более практичны, чем стандартные методы OO для написания модульного и несвязанного программного обеспечения. Главное его преимущество заключается в том, что он позволяет расширить уже существующие типы новыми функциональными возможностями, не изменяя их.

Существует множество не упомянутых деталей, таких как синтаксический сахар, def экземпляры и т. д. Это огромная тема, и, к счастью, она имеет большое освещение во всей сети. Просто погуглите для "scala Type class".

Есть много преимуществ, помимо вашего примера. Я приведу только один; в то же время, это также трюк, который вы можете использовать в определенных случаях.

Представьте, что вы создаете признак, который является универсальным контейнером для других значений, таких как список, набор, дерево или что-то в этом роде.
trait MyContainer[A] {
  def containedValue:A
}
Теперь, в какой-то момент, Вы найдете полезным перебрать все элементы содержащегося значения. Конечно, это имеет смысл только в том случае, если содержащееся значение является итерационным тип. Но поскольку вы хотите, чтобы ваш класс был полезен для всех типов, вы не хотите ограничивать A типом Seq, или Traversable, или чем-то подобным. В принципе, вам нужен метод, который говорит: "Я могу быть вызван только в том случае, если A имеет тип Seq." И если кто-то вызывает его, скажем, MyContainer[Int], это должно привести к ошибке компиляции.

Это возможно. Вам нужно некотороедоказательство , что A имеет тип последовательности. И вы можете сделать это с помощью Scala и implicite аргументы:

trait MyContainer[A] {
  def containedValue:A
  def aggregate[B](f:B=>B)(implicit ev:A=>Seq[B]):B =
    ev(containedValue) reduce f
}
Таким образом, если вы вызовете этот метод на MyContainer[Seq[Int]], компилятор будет искать неявный Seq[Int]=>Seq[B]. Это очень просто решить для компилятора. Потому что существует глобальная неявная функция, которая называется identity, и она всегда находится в области видимости. Его сигнатура типа выглядит примерно так: A=>A

Он просто возвращает любой аргумент, переданный ему.

Я не знаю, как называется этот паттерн. (Может ли кто-нибудь помочь?) Но я думаю, что это ловкий трюк, который очень пригодится. иногда. Вы можете увидеть хороший пример этого в библиотеке Scala, если посмотрите на сигнатуру метода Seq.sum. В случае sum используется другой неявный тип параметра; в этом случае неявный параметр являетсядоказательством того, что содержащийся тип является числовым, и поэтому сумма может быть построена из всех содержащихся значений.

Это не только использование имплицитов, и, конечно, не самое заметное, но я бы сказал, что это почетное упоминание. :- )

Comments

    Ничего не найдено.