Что значит «абстрагироваться над»? - PullRequest
91 голосов
/ 22 января 2011

Часто в литературе по Scala я сталкиваюсь с фразой "абстрактное окончание", но я не понимаю намерения. Например , Мартин Одерский пишет

Вы можете передавать методы (или "функции") в качестве параметров, или вы можете абстрагироваться над их. Вы можете указать типы в качестве параметров, или вы можете абстрагироваться от их.

В качестве другого примера, в «Устаревшая модель наблюдателя» ,

Следствием того, что наши потоки событий являются первоклассными значениями, является то, что мы можем абстрагироваться над ими.

Я читал, что дженерики первого порядка "абстрактные над типами", а монады "абстрактные над конструкторами типов". И мы также видим подобные фразы на бумаге Cake Pattern . Процитирую один из многих таких примеров:

Члены абстрактного типа предоставляют гибкий способ абстрагирования над конкретными типами компонентов.

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

Итак ... что на самом деле означает "абстракция над"?

Ответы [ 5 ]

116 голосов
/ 22 января 2011

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

Например, рассмотрим программу, которая принимает суммучисла 1, 2 и 3:

val sumOfOneTwoThree = 1 + 2 + 3

Эта программа не очень интересна, поскольку она не очень абстрактна.Мы можем абстрагироваться от чисел, которые мы суммируем, объединяя все списки чисел под одним символом ns:

def sumOf(ns: List[Int]) = ns.foldLeft(0)(_ + _)

И нам не особо важно, что этоСписок либо.List - это конструктор определенного типа (принимает тип и возвращает тип), но мы можем абстрагироваться над конструктором типа, указав, какую существенную характеристику мы хотим (чтобы ее можно было сложить):

trait Foldable[F[_]] {
  def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
}

def sumOf[F[_]](ns: F[Int])(implicit ff: Foldable[F]) =
  ff.foldl(ns, 0, (x: Int, y: Int) => x + y)

И мы можем иметь неявные Foldable экземпляры для List и любой другой вещи, которую мы можем сложить.

implicit val listFoldable = new Foldable[List] {
  def foldl[A, B](as: List[A], z: B, f: (B, A) => B) = as.foldLeft(z)(f)
}

val sumOfOneTwoThree = sumOf(List(1,2,3))

Более того, мы можем абстрагироваться над как операцией, так и типом операндов:

trait Monoid[M] {
  def zero: M
  def add(m1: M, m2: M): M
}

trait Foldable[F[_]] {
  def foldl[A, B](as: F[A], z: B, f: (B, A) => B): B
  def foldMap[A, B](as: F[A], f: A => B)(implicit m: Monoid[B]): B =
    foldl(as, m.zero, (b: B, a: A) => m.add(b, f(a)))
}

def mapReduce[F[_], A, B](as: F[A], f: A => B)
                         (implicit ff: Foldable[F], m: Monoid[B]) =
  ff.foldMap(as, f)

Теперь у нас есть нечто довольно общее.Метод mapReduce сворачивает любые F[A], учитывая, что мы можем доказать, что F является складным и что A является моноидом или может быть преобразован в один.Например:

case class Sum(value: Int)
case class Product(value: Int)

implicit val sumMonoid = new Monoid[Sum] {
  def zero = Sum(0)
  def add(a: Sum, b: Sum) = Sum(a.value + b.value)
}

implicit val productMonoid = new Monoid[Product] {
  def zero = Product(1)
  def add(a: Product, b: Product) = Product(a.value * b.value)
}

val sumOf123 = mapReduce(List(1,2,3), Sum)
val productOf456 = mapReduce(List(4,5,6), Product)

У нас есть абстрагированных над моноидами и складными.

10 голосов
/ 22 января 2011

В первом приближении, способность «абстрагироваться» от чего-либо означает, что вместо того, чтобы использовать это что-то напрямую, вы можете сделать его параметром или иным образом использовать его «анонимно».

Scala позволяет абстрагироваться над типами, позволяя классам, методам и значениям иметь параметры типа, а значениям иметь абстрактные (или анонимные) типы.

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

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

Scala позволяет абстрагироваться от параметров типа, позволяяпараметры типа заказа.

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

Scala позволяет абстрагироваться от «вещей, которые можно использовать как что-то другое», позволяя неявные преобразования в качестве параметров.Haskell делает то же самое с классами типов.

Scala (пока) не позволяет вам абстрагироваться от классов.Вы не можете передать класс чему-либо, а затем использовать этот класс для создания новых объектов.Другие языки допускают абстракцию над классами.

(«Абстракция монад над конструкторами типов» верна только в очень ограничительной форме. Не зацикливайтесь на этом, пока у вас не появится момент «Ага! Я понимаю монады !!».)

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

6 голосов
/ 22 января 2011

Абстракция - это своего рода обобщение.

http://en.wikipedia.org/wiki/Abstraction

Не только в Scala, но и во многих языках необходимы такие механизмы, чтобы уменьшить сложность (или, по крайней мере, создать иерархию, которая разбивает информацию на более простые для понимания части).

Класс - это абстракция над простым типом данных. Это похоже на базовый тип, но на самом деле обобщает их. Таким образом, класс - это больше, чем простой тип данных, но он имеет много общего с ним.

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

В данном случае он, в частности, подразумевает процесс абстрагирования проблемы и создания такого же решения проблемы. В C очень мало возможностей для абстрагирования (вы можете сделать это, но это очень быстро запутывается, и язык не поддерживает это напрямую). Если вы написали это на C ++, вы могли бы использовать открытые концепции, чтобы уменьшить сложность проблемы (ну, это та же сложность, но концептуализация, как правило, проще (по крайней мере, если вы научитесь мыслить в терминах абстракций)).

Например, если мне нужен был специальный тип данных, который был похож на int, но, скажем, ограниченный, я мог бы абстрагироваться над ним, создав новый тип, который мог бы использоваться как int, но имел те свойства, которые мне были нужны. Процесс, который я бы использовал, чтобы сделать такую ​​вещь, был бы назван «абстрагированием».

5 голосов
/ 22 января 2011

Вот моя узкая интерпретация шоу и рассказа. Это говорит само за себя и работает в REPL.

class Parameterized[T] { // type as a parameter
  def call(func: (Int) => Int) = func(1)  // function as a parameter
  def use(l: Long) { println(l) } // value as a parameter
}

val p = new Parameterized[String] // pass type String as a parameter
p.call((i:Int) => i + 1) // pass function increment as a parameter
p.use(1L) // pass value 1L as a parameter


abstract class Abstracted { 
  type T // abstract over a type
  def call(i: Int): Int // abstract over a function
  val l: Long // abstract over value
  def use() { println(l) }
}

class Concrete extends Abstracted { 
  type T = String // specialize type as String
  def call(i:Int): Int = i + 1 // specialize function as increment function
  val l = 1L // specialize value as 1L
}

val a: Abstracted = new Concrete
a.call(1)
a.use()
2 голосов
/ 22 января 2011

Другие ответы уже дают хорошее представление о том, какие существуют виды абстракций. Давайте рассмотрим кавычки одну за другой и приведем пример:

Вы можете передавать методы (или «функции») в качестве параметров, или вы можете абстрагироваться Над ними. Вы можете указать типы как параметры, или вы можете абстрагироваться над им.

Передать функцию в качестве параметра: List(1,-2,3).map(math.abs(x)) Ясно, что abs передается здесь как параметр. map сам по себе абстрагируется от функции, которая делает определенную специальную вещь с каждым элементом списка. val list = List[String]() указывает параметр типа (String). Вы можете написать тип коллекции, в котором вместо этого используются члены абстрактного типа: val buffer = Buffer{ type Elem=String }. Одно из отличий состоит в том, что вы должны писать def f(lis:List[String])..., но def f(buffer:Buffer)..., поэтому тип элемента во втором методе как бы «скрыт».

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

В Swing событие просто «происходит» неожиданно, и вам приходится иметь дело с ним здесь и сейчас. Потоки событий позволяют вам выполнить всю прокладку проводов более декларативным способом. Например. когда вы хотите сменить ответственного слушателя в Swing, вы должны отменить регистрацию старого и зарегистрировать нового, а также знать все кровавые подробности (например, проблемы с потоками). С потоками событий источник событий становится тем, что вы можете просто обойти, делая его не сильно отличающимся от потока байтов или символов, следовательно, более «абстрактной» концепции.

члены абстрактного типа обеспечивают гибкость способ абстрагироваться от конкретных типов компоненты.

Приведенный выше класс Buffer уже является примером для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...