Статьи

Mixins упрощают состав в Scala


У Scala есть отличная особенность под названием «черты».
  Черта — это средство инкапсуляции методов и полей, которое в некоторых отношениях ведет себя как интерфейс, а в других — как абстрактный класс.
  Подобно интерфейсу или абстрактному классу, черта может объявлять методы и поля, которые впоследствии должны быть определены в каком-то конкретном классе.
  Подобно абстрактному классу, но в отличие от интерфейса, черта также может предоставлять тела и значения по умолчанию для этих методов и полей.
  Однако, в отличие от класса, черта не может иметь параметров конструктора.
  Вот простая черта, которая определяет итератор:

trait MyIter [A] {

  def next(): A

  def hasNext: Boolean

}

Содержание этой статьи было первоначально размещено в блоге Нила Кэрриера ,
Techneilogy

В первой из этих ролей черты могут использоваться точно так же, как традиционные интерфейсы: как способ указать внешнее поведение объекта, ничего не говоря о его реализации.   Во второй из этих ролей черты могут использоваться как абстрактные базовые классы, предоставляя как объявления, так и определения. 

 

Однако — и это действительно классная часть — поскольку признаки не являются классами, для одного объекта можно указать несколько признаков без нарушения ограничений одиночного наследования.   Таким образом, как и интерфейсы, несколько признаков могут вносить вклад в объявление класса, но в отличие от интерфейсов или отдельных базовых классов, несколько признаков также могут вносить вклад в определение класса.   Это иногда называют « смешанным » наследованием, потому что возможности «смешаны» с другими типами.

 

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

 

 (И, как всегда, код и информация здесь представлены «как есть» и без каких-либо гарантий или подразумеваемой пригодности; используйте их на свой страх и риск.)

 

(Код представлен фрагментами; полный список приведен внизу.)

 

Вспоминая простой пример итератора сверху:

trait MyIter [A] {

  def next(): A

  def hasNext: Boolean

}

Эта черта параметризована для повторного типа, скобки «[]» обозначают параметры типа в Scala.   Кроме того, методы следуют стандартной практике Scala для методов без аргументов: если метод изменяет объект, используются круглые скобки, если он не изменяет объект, круглые скобки опускаются.

 

В этом случае итератор предоставляет только объявления и поэтому используется в роли интерфейса.   (Фактически, это напоминает фактическую черту Iterator из Scala.)   Мы могли бы предоставлять функциональность через другую черту или конкретный класс, но мы фактически сделаем это, используя абстрактный класс.   Абстрактный класс ниже определяет итерацию в диапазоне целых чисел и делегирует преобразование целочисленного типа в итеративный тип его производным классам с использованием абстрактной функции current.   Наследующие могут получить базовую возможность итерации, просто определив эту функцию.

 

abstract class MyCursorIter[A] (

  protected val first: Int,

  protected val last: Int) extends MyIter[A] {

  protected var cursor = first - 1

  protected def current: A

  def next() = {

    if (hasNext) {

      cursor += 1

      current

    }

    else throw new Exception("Iteration past end.")

  }

  def hasNext = cursor < last

}

 

Обратите внимание, что первая черта или базовый класс смешиваются с использованием ключевого слова «extends»; последующие черты смешиваются при использовании ключевого слова «с».

 

Если у вас есть итератор, вы можете использовать его для выполнения всяких замечательных вещей.   Но, пожалуй, самая замечательная из всех функций высшего порядка: «сложить».   Фолд является хорошим кандидатом на вторую роль черт: определение методов и ценностей.   Ниже приведено определение признака сгиба применительно к признаку « MyIter »:

trait MyFold[A] {

  this: MyIter[A] =>

  @tailrec

  final def fold[B] (acc: B, f:(B,A) => B): B = {

    if (hasNext) {

      fold(f(acc,next()),f)

    } else acc

  }

}

Странно выглядящая строка «this: MyIter [A] =>» сообщает компилятору, что эта черта будет применяться только к типам, которые также поддерживают черту « MyIter [A]».   Это то, что позволяет вам использовать члены « hasNext » и «next ()» из этой черты.   «@ Tailrec » в функции сгиба сообщает компилятору Scala, что функцию следует сделать хвостовой рекурсивной, и предупреждает вас во время компиляции, если это невозможно.   Ниже приводится стандартная складкой функции , которая принимает первоначальный результат « согласно » (для соотв umulator), а также шаги , через элементы , проходящие от результата применения функции «F» на предыдущий результат и каждый элемент, наконец , возвращая последний результат ,

 

Fold похож на швейцарский армейский нож функций более высокого порядка в том смысле, что его можно использовать для реализации целого ряда других функций более высокого порядка.   Фрагмент ниже показывает пару других миксинов, которые используют фолд Первый вычисляет длину повторяющейся последовательности, а второй преобразует повторяемую последовательность в список.   Обратите внимание на использование конструкции «this:… =>», чтобы сообщить компилятору, что эти черты расширяют типы с чертой свертки.   Также обратите внимание на использование «_» в «this: MyFold [_] =>» в MyLength, которое указывает, что параметр типа не важен для этого определения.   Также обратите внимание, что обе функции используют итератор, оставляя его пустым.

trait MyLength {

  this: MyFold[_] =>

  def length() = {

    fold(0, (b: Int, _) => b + 1)

  }

}

trait MyToList[A] {

  this: MyFold[A] =>

  def toList() =

    fold(Nil, (b: List[A], a: A) => a::b).reverse

}

И, для удобства, вот особенность, которая смешивает функцию сброса с классом « MyCursorIter ».   Если итератор можно сделать сбрасываемым, можно написать функции, которые его используют, не делая его бесполезным для дальнейших вычислений.

 

trait MyReset {

  this: MyCursorIter[_] =>

  def reset() = cursor = first - 1

}

Наконец, время для первой конкретной реализации.   И вот он, « MyStringIter », символьный итератор между строками:

 

class MyStringIter (s: String)

  extends MyCursorIter[Char](0,s.length - 1)

  with MyReset

  with MyFold[Char]

  with MyLength

  with MyToList[Char] {

  protected def current = s(cursor)

}

 

И тест:

object Test00 {

  def main(args: Array[String]): Unit = {

    // String example.

    val msi = new MyStringIter("This is a test.")

    println(msi.toList())

    msi.reset()

    println(msi.length())

    println("Your breakpoint here.")

  }

}

 

Но ждать!   Есть еще кое-что!   В Scala вы можете использовать все эти черты и классы, чтобы объединять типы на лету.   Расширенный тест, приведенный ниже, иллюстрирует это, определяя с помощью самой функции теста специальный итератор для целочисленных диапазонов.

 

object Test00 {

  def main(args: Array[String]): Unit = {

    // String example.       

    val msi = new MyStringIter("This is a test.")

    println(msi.toList())

    msi.reset()

    println(msi.length())

    // Range example.

    val msr =

      new MyCursorIter[Int](0,9)

        with MyReset

        with MyFold[Int]

        with MyToList[Int]

        { protected def current = cursor }

    println(msr.toList())
   
    msr.reset()

    // Error!  MyLength is not mixed-in to msr.

    // println(msr.length())

    println("Your breakpoint here.")

  }

}

 

Разве это не круто?

-Neil

Источник:  http://techneilogy.blogspot.com/