Що означає «абстрактне над»?


95

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

Ви можете передавати методи (або "функції") як параметри, або можете абстрагуватися над ними. Ви можете вказати типи як параметри, або ви можете абстрагуватися над ними.

В якості іншого прикладу, в «протестуючий патерн Observer» паперу,

Наслідок того, що наші потоки подій є першокласними значеннями, ми можемо абстрагуватися над ними.

Я читав, що узагальнені засоби першого порядку "абстрактні над типами", тоді як монади "абстрактні над конструкторами типів". І ми також бачимо подібні фрази у статті " Торт" . Наведу один із багатьох таких прикладів:

Члени абстрактного типу забезпечують гнучкий спосіб абстрагування над конкретними типами компонентів.

Навіть відповідні запитання щодо переповнення стека використовують цю термінологію. "не може екзистенційно абстрагуватися над параметризованим типом ..."

Отже ... що насправді означає "абстрактне над"?

Відповіді:


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 ви використовуєте, і яку помилку ви отримали?
Daniel C. Sobral

1
@Apocalisp Було б цікаво, якби ви зробили один із двох останніх прикладів а Setчи інший складний тип. Приклад з a Stringта конкатенацією також буде дуже крутим.
Daniel C. Sobral

1
Прекрасна відповідь, Рунар. Дякую! Я наслідував пропозицію Даніеля і створив неявний setFoldable і concatMonoid, не змінюючи mapReduce взагалі. Я вже на шляху до цього.
Morgan Creighton

6
Мені знадобився момент, щоб зрозуміти, що в останніх 2 рядках ви користуєтесь тим фактом, що супутні об’єкти суми та продукту, оскільки вони визначають застосувати (Int), трактуються Scala як Int => Sum і Int => Product компілятор. Дуже хороша!
Kris Nuttycombe

Гарний пост :)! У вашому останньому прикладі імпліцитна логіка Monoid видається непотрібною. Це простіше: gist.github.com/cvogt/9716490
cvogt

11

Для першого наближення, можливість "абстрагуватися" над чимось означає, що замість того, щоб використовувати це щось безпосередньо, ви можете створити його параметр, або використати це "анонімно".

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

Scala дозволяє вам абстрагуватися від дій, дозволяючи методам мати параметри функції.

Scala дозволяє вам абстрагуватися від об’єктів, дозволяючи структурно визначати типи.

Scala дозволяє абстрагувати параметри типу, дозволяючи параметри типу вищого порядку.

Scala дозволяє абстрагуватися від шаблонів доступу до даних, дозволяючи створювати екстрактори.

Scala дозволяє абстрагуватися від "речей, які можна використовувати як щось інше", дозволяючи неявні перетворення як параметри. Хаскелл робить подібне з класами типів.

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

("Анотація монад над конструкторами типів" відповідає дійсності лише дуже обмежувально. Не зациклюйтеся на цьому, поки не з’явиться момент "Ага! Я розумію монади !!".)

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


1
Ви можете передати a Manifest, або навіть a Class, і використовувати відображення для створення екземплярів нових об’єктів цього класу.
Daniel C. Sobral

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()

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 вище вже є прикладом цього.


0

Наведені вище відповіді дають чудове пояснення, але, щоб підсумувати це в одному реченні, я б сказав:

Абстрагуватися над чимось - це те саме, що нехтувати цим, де це не має значення .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.