В Scala что означает" extends (A => B) " для класса case?

исследуя, как сделать Мемуаризацию в Scala, я нашел некоторый код, который я не grok. Я пытался найти эту конкретную "вещь", но не знаю, как ее назвать; то есть термин, которым к ней обращаться. Кроме того, это не простой поиск с помощью символа, тьфу!

Я видел следующий код для memoization в Scala здесь:

case class Memo[A,B](f: A => B) extends (A => B) {
  private val cache = mutable.Map.empty[A, B]
  def apply(x: A) = cache getOrElseUpdate (x, f(x))
}

и это то, что класс case расширяет, что меня смущает,extends (A => B) часть. Во-первых, что происходит? Во-вторых, зачем она вообще нужна? И, наконец, как вы называете этот вид наследования, т. е. есть ли какое-то конкретное имя или термин, который я могу использовать для его обозначения?

далее, я вижу записку таким образом, чтобы рассчитать количество Fibanocci здесь:

  val fibonacci: Memo[Int, BigInt] = Memo {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n-1) + fibonacci(n-2)
  }

наверное, я не вижу всех "упрощений", которые применяются. Но, я не в состоянии понять конец val линия, = Memo {. Итак, если это было набрано больше дословно, возможно, я бы понял "скачок", сделанный относительно того, как строится памятка.

любая помощь с благодарностью. Спасибо.

5 ответов


A => B сокращенно Function1[A, B], так как Memo расширяет функцию от A to B, наиболее заметно определяется с помощью метода apply(x: A): B который должен быть определен.

из-за обозначения" infix " вам нужно поставить круглые скобки вокруг типа, т. е. (A => B). Вы также можете написать

case class Memo[A, B](f: A => B) extends Function1[A, B] ...

или

case class Memo[A, B](f: Function1[A, B]) extends Function1[A, B] ...

чтобы завершить ответ 0_,fibonacci будет instanciated через применение метода Memoсопутствующий объект, автоматически генерируемый компилятором с Memo является классом case.

это означает, что для вас генерируется следующий код:

object Memo {
  def apply[A, B](f: A => B): Memo[A, B] = new Memo(f)
}

Scala имеет специальную обработку для apply метод: его имя не нужно вводить при вызове. Два следующих вызова строго эквивалентны:

Memo((a: Int) => a * 2)

Memo.apply((a: Int) => a * 2)

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

если вы последуете этому ссылка, вы увидите, что PartialFunction выходит Function1 - что является ожидаемым аргументом Memo.apply.

ну и что, что код на самом деле означает, когда обессахаренный (если это слово), является:

lazy val fibonacci: Memo[Int, BigInt] = Memo.apply(new PartialFunction[Int, BigInt] {
  override def apply(v: Int): Int =
    if(v == 0)      0
    else if(v == 1) 1
    else            fibonacci(v - 1) + fibonacci(v - 2)

  override isDefinedAt(v: Int) = true
})

обратите внимание, что я значительно упростил способ сопоставления шаблонов, но я думал, что начинаю обсуждение unapply и unapplySeq было бы не по теме и запутанно.


Я оригинал автор делает memoization таким образом. Вы можете увидеть некоторые примеры использования в том же файле. Он также работает очень хорошо, когда вы хотите запомнить несколько аргументов из-за того, как Scala разворачивает кортежи:

    /**
     * @return memoized function to calculate C(n,r) 
     * see http://mathworld.wolfram.com/BinomialCoefficient.html
     */
     val c: Memo[(Int, Int), BigInt] = Memo {
        case (_, 0) => 1
        case (n, r) if r > n/2 => c(n, n-r)
        case (n, r) => c(n-1, r-1) + c(n-1, r)
     }
     // note how I can invoke a memoized function on multiple args too
     val x = c(10, 3) 

этот ответ представляет собой синтез частичных ответов, предоставленных как 0__, так и Николасом Ринаудо.

резюме:

есть много удобных (но также сильно переплетенных) предположений, сделанных компилятором Scala.

  1. Scala лечит extends (A => B) как синоним extends Function1[A, B] (ScalaDoc для Function1[+T1, - R])
  2. конкретная реализация унаследованного абстрактного метода Function1 apply(x: A): B необходимо обеспечить;def apply(x: A): B = cache.getOrElseUpdate(x, f(x))
  3. Scala предполагает подразумеваемое match для блока кода, начинающегося с = Memo {
  4. Scala передает содержимое между {} начато в пункте 3 в качестве параметра конструктора класса Memo case
  5. Scala предполагает подразумеваемый тип между {} начато в пункте 3 как PartialFunction[Int, BigInt] и компилятор использует блок кода "match" в качестве переопределения для метода PartialFunction apply() и затем предоставляет дополнительное переопределение для метод частичной функции isDefinedAt().

детали:

первый блок кода, определяющий Memo класса case, может быть написан более подробно как таковой:

case class Memo[A,B](f: A => B) extends Function1[A, B] {    //replaced (A => B) with what it's translated to mean by the Scala compiler
  private val cache = mutable.Map.empty[A, B]
  def apply(x: A): B = cache.getOrElseUpdate(x, f(x))  //concrete implementation of unimplemented method defined in parent class, Function1
}

второй блок кода, определяющий val fibanocci, может быть написан более подробно как таковой:

lazy val fibonacci: Memo[Int, BigInt] = {
  Memo.apply(
    new PartialFunction[Int, BigInt] {
      override def apply(x: Int): BigInt = {
        x match {
          case 0 => 0
          case 1 => 1
          case n => fibonacci(n-1) + fibonacci(n-2)
        }
      }
      override def isDefinedAt(x: Int): Boolean = true
    }
  )
}

пришлось добавить lazy к валу второго блока кода для того чтобы общаться с само-референтной проблемой в строке case n => fibonacci(n-1) + fibonacci(n-2).

и наконец, пример использования Фибоначчи:

val x:BigInt = fibonacci(20) //returns 6765 (almost instantly)

еще одно слово об этом extends (A => B): the extends здесь не требуется, но необходимо, если экземпляры Memo должны использоваться в функциях более высокого порядка или ситуациях.

без этого extends (A => B), это совершенно нормально, если вы используете Memo экземпляр fibonacci в вызовы методов.

case class Memo[A,B](f: A => B) {
    private val cache = scala.collection.mutable.Map.empty[A, B]
    def apply(x: A):B = cache getOrElseUpdate (x, f(x))
}
val fibonacci: Memo[Int, BigInt] = Memo {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n-1) + fibonacci(n-2)
}

например:

Scala> fibonacci(30)
res1: BigInt = 832040

но когда вы хотите использовать его в функциях более высокого порядка, у вас будет несоответствие типа ошибка.

Scala> Range(1, 10).map(fibonacci)
<console>:11: error: type mismatch;
 found   : Memo[Int,BigInt]
 required: Int => ?
              Range(1, 10).map(fibonacci)
                               ^

так extends здесь только помогает ID экземпляра fibonacci другим, что у него есть apply метод и, таким образом, может выполнять некоторые задания.