Что означает «абстрагироваться»?

95

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

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

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

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

Я читал, что обобщения первого порядка «абстрагируются над типами», а монады «абстрагируются над конструкторами типов». И мы также видим подобные фразы в газете «Выкройка торта» . Приведу один из многих таких примеров:

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

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

Итак ... что на самом деле означает «абстрагироваться»?

Морган Крейтон
источник

Ответы:

124

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

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

val sumOfOneTwoThree = 1 + 2 + 3

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

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

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

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)

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

Апокалисп
источник
@coubeatczech Код работает на REPL нормально. Какую версию Scala вы используете и какую ошибку вы получили?
Дэниел С. Собрал,
1
@Apocalisp Было бы интересно, если бы вы сделали один из двух последних примеров a Setили какой-нибудь другой складной тип. Пример с Stringконкатенацией и тоже был бы довольно крутым.
Дэниел С. Собрал,
1
Прекрасный ответ, Рунар. Спасибо! Я последовал предложению Дэниела и создал неявные setFoldable и concatMonoid, вообще не меняя mapReduce. Я уже на пути к этому.
Морган Крейтон
6
Мне потребовалось время, чтобы понять, что в последних двух строках вы используете тот факт, что сопутствующие объекты Sum и Product, поскольку они определяют apply (Int), обрабатываются Scala как Int => Sum и Int => Product. компилятор. Очень хорошо!
Крис Наттикомб
Хороший пост :)! В вашем последнем примере неявная логика Monoid кажется ненужной. Это проще: gist.github.com/cvogt/9716490
cvogt
11

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

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

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

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

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

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

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

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

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

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

Дэйв Гриффит
источник
1
Вы можете передать a Manifestили даже a Classи использовать отражение для создания экземпляров новых объектов этого класса.
Дэниел С. Собрал,
6

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

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

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

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

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

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

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

АннотацияДиссонанс
источник
5

Вот моя узкая интерпретация шоу и рассказа. Это не требует пояснений и работает в 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()
Huynhjl
источник
1
в значительной степени идея «абстрактного над» в коде - мощная, но короткая, попробую этот язык +1
user44298
2

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

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

Передать функцию как параметр: здесь 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 уже является примером этого.

Landei
источник
1

Ответы выше дают прекрасное объяснение, но, резюмируя его в одном предложении, я бы сказал:

Абстрагироваться от чего-либо - это то же самое, что игнорировать это там, где это не имеет значения .

Михал Качанович
источник