значение по умолчанию для функций в параметрах в Scala

Я изучал и экспериментировал со Scala. Я хотел реализовать функцию с универсальным типом, которая принимает функцию в качестве параметра и предоставляет реализацию этой функции по умолчанию.

Теперь, когда я пробую это без универсального типа, он работает:

def defaultParamFunc(z: Int, y: Int)(f: (Int, Int) => Int = (v1: Int,v2: Int) => { v1 + v2 }) : Int = {
  val ans = f(z,y)
  println("ans : " + ans)
  ans
}

это не дает никакой ошибки

но когда я пытаюсь сделать то же самое с универсальным типом,

def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 }) = {
  val ans = f(z,y)
  println("ans : " + ans)
  ans
}

Я получаю сообщение об ошибке:

[error]  found   : B
[error]  required: String
[error]  def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 }) = {
[error]                                                                               ^

Является ли ошибка тем, что компилятор не знает, будет ли добавляться тип B? потому что, когда я просто возвращаю v1 или v2 вместо v1 + v2, это работает.

def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 }) = {
  val ans = f(z,y)
  println("ans : " + ans)
  ans
}

Если да, то как указать, что указанный тип должен быть числовым? Я попытался заменить B на B : Numeric, но все равно выдает ту же ошибку.


person Aditya Pawade    schedule 18.04.2014    source источник


Ответы (2)


Первая проблема с вашим кодом — это небольшое упущение: вам нужно импортировать члены экземпляра Numeric, чтобы ввести + в область видимости (на самом деле это поместит num.mkNumericOps в область видимости, включив метод +): Давайте попробуем это:

def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 })(implicit num: Numeric[B]) = {
  // Brings `+` into scope.
  // Note that you can also just import Numeric.Implicits._ for the same effect
  import num._

  val ans = f(z,y)
  println("ans : " + ans)
  ans
}

К сожалению, это все еще не компилируется. Проблема здесь в том, что значение параметра по умолчанию может ссылаться только на параметр из более раннего списка параметров. Поскольку num находится в последнем списке параметров, его невозможно использовать в значении по умолчанию. Вот незадача.

Итак, давайте попробуем перехитрить компилятор и разделить метод на две части, чтобы мы могли иметь неявный параметр num перед параметром f:

def defaultParamFunc[B](z: B, y: B)(implicit num: Numeric[B]) = new {
  // NOTE: using structural typing here is rather inefficient.
  //       Let's ignore that for now.
  import num._
  def apply(f: (B, B) => B = (v1: B, v2: B) => { v1 + v2 }) = {
    val ans = f(z,y)
    println("ans : " + ans)
    ans
  }
}

Сладко, он компилируется. Что мы сделали здесь, так это то, что defaultParamFunc фактически возвращает (псевдо) экземпляр функции. Эта функция принимает f в качестве параметра, и, поскольку мы создаем экземпляр функции в теле defaultParamFunc, нет проблем со ссылкой на num.

Однако не будем рано радоваться. Если мы попытаемся вызвать его, не указывая параметр f, компилятору это не понравится:

scala> defaultParamFunc(5, 7)()
<console>:17: error: not enough arguments for method defaultParamFunc: (implicit num: Numeric[Int])((Int, Int) => Int) => Int{def apply$default$1: (Int, Int) => Int}.
Unspecified value parameter num.
          defaultParamFunc(5, 7)()

Упс. Компилятор считает, что пустой список параметров относится ко второму (неявному) списку параметров в defaultParamFunc, а не к списку параметров экземпляра (псевдо) функции.

Придется найти другой способ, не требующий неявного параметра в методе defaultParamFunc. Магнитные узоры спешат на помощь!

trait MyMagnet[B] extends ((B,B) => B)
object MyMagnet {
  implicit def fromFunc[B]( f: (B, B) => B ) = new MyMagnet[B] {
    def apply(z: B, y: B): B = {
      val ans = f(z,y)
      println("ans : " + ans)
      ans
    }
  }
  implicit def fromUnit[B](unit: Unit)(implicit num: Numeric[B]) = new MyMagnet[B]{
    import num._
    def apply(v1: B, v2: B): B = { v1 + v2 }
  }
}
def defaultParamFunc[B](z: B, y: B)(magnet: MyMagnet[B]): B = magnet(z, y)

Конечно, это немного надумано для такого крошечного результата. Но это работает:

scala> defaultParamFunc(2,3)()
res15: Int = 5

scala> defaultParamFunc(2,3){(x:Int, y:Int) => x*y }
ans : 6
res16: Int = 6  

Однако обратите внимание, что, используя шаблон магнита, мы потеряли преимущества вывода типов. Поэтому мы не можем просто сделать следующее:

scala> defaultParamFunc(2,3)(_ * _)
<console>:18: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$times(x$2))
              defaultParamFunc(2,3)(_ * _)      
person Régis Jean-Gilles    schedule 19.04.2014
comment
в порядке. Я попробовал ваш код. Это не компилируется. Когда я использую defaultParamFunc(2,3)(), он выдает ошибку «Неопределенное значение параметра магнит». когда я попробовал defaultParamFunc(2,3){(x:Int, y:Int) => x*y }, я получил сообщение об ошибке: (Int, Int) => Int required: test.ScalaTest.MyMagnet[Int ] Примечание: неявный метод fromUnit здесь неприменим, поскольку он идет после точки приложения и в нем отсутствует явный тип результата. - person Aditya Pawade; 20.04.2014
comment
Что ж, он компилируется (проверено в REPL, scala версии 2.10.1). В REPL вам просто нужно позаботиться о том, чтобы оценивать MyMagnet trait и MyMagnet object вместе, иначе последний не будет рассматриваться как сопутствующий объект первого. Однако из сообщения об ошибке я вижу, что вы не используете REPL. Все, что я могу предложить, это опубликовать точный код (как и во всем исходном файле (файлах)), чтобы я мог видеть, что не так. - person Régis Jean-Gilles; 20.04.2014
comment
Прости. Я новичок в скале. Я не использую РЕПЛ. Я запускаю его с помощью SBT. Моя версия scala 2.10.3. Вот файл: pastebin.com/HDgprLZD - person Aditya Pawade; 20.04.2014
comment
ОК .. теперь это работает .. глупая ошибка .. Большое спасибо за подробное объяснение. Это было очень информативно. :) - person Aditya Pawade; 20.04.2014

Является ли ошибка тем, что компилятор не знает, будет ли добавляться тип B?

да. Параметр типа B в вашем примере не имеет ограничений — вы можете использовать любой тип в качестве аргумента при использовании этой функции. У компилятора нет возможности заранее проверить, имеет ли тип, который вы используете, метод +.

Вы можете использовать подход класса типов, вот кое-что, чтобы дать вам представление:

// This defines a generic 'plus' method
trait Plus[T] {
  def plus(x: T, y: T): T
}

// This is a generic method that can do 'plus' on whatever type
// for which there is an implicit Plus value in scope
def add[T : Plus](a: T, b: T) = implicitly[Plus[T]].plus(a, b)

// This defines what 'plus' means for Ints
implicit val intPlus = new Plus[Int] {
  def plus(x: Int, y: Int): Int = x + y
}

// This defines what 'plus' means for Strings
implicit val stringPlus = new Plus[String] {
  def plus(x: String, y: String): String = x.concat(y)
}

// Examples of use
println(add(2, 3))
println(add("hello", "world"))

edit: На самом деле, Numeric Scala уже делает это за вас:

def add[T : Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x, y)

// Use it with integers
println(add(2, 3))

// Or doubles
println(add(1.5, 2.4))

// Or with for example BigDecimal
println(add(BigDecimal("1.234"), BigDecimal("4.567")))

Итак, вы должны быть в состоянии сделать что-то вроде этого:

def defaultParamFunc[B : Numeric](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { implicitly[Numeric[B]].plus(v1, v2) }) = {
  val ans = f(z,y)
  println("ans : " + ans)
  ans
}
person Jesper    schedule 18.04.2014
comment
Это не компилируется (пожалуйста, смотрите мой ответ) - person Régis Jean-Gilles; 19.04.2014
comment
Спасибо за ваш ответ. Первый пример работал, но с использованием числового кода я получил ошибку «не удалось найти неявное значение для параметра e: Numeric[B]» в функции defaultParamFunc. функция добавления работала - person Aditya Pawade; 20.04.2014