Scala: зачем использовать неявный аргумент функции?
У меня есть следующая функция:
def getIntValue(x: Int)(implicit y: Int ) : Int = {x + y}
Я вижу выше объявление везде. Я понимаю, что делает эта функция. Это функция карринга, которая принимает два аргумента. Если вы опустите второй аргумент, он вызовет неявное определение, которое вместо этого возвращает int. Поэтому я думаю, что это нечто очень похожее на определение значения по умолчанию для аргумента.
implicit val temp = 3
scala> getIntValue(3)
res8: Int = 6
Мне было интересно, каковы преимущества вышеупомянутого заявления?
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