Apa yang dimaksud dengan “abstract over”?

95

Seringkali dalam literatur Scala, saya menemukan frase "abstrak berakhir", tetapi saya tidak mengerti maksudnya. Misalnya , tulis Martin Odersky

Anda dapat mengirimkan metode (atau "fungsi") sebagai parameter, atau Anda dapat mengabstraksikannya . Anda dapat menentukan tipe sebagai parameter, atau Anda dapat mengabstraksikannya .

Sebagai contoh lain, dalam makalah "Menghentikan Pola Pengamat" ,

Konsekuensi dari aliran acara kami menjadi nilai kelas satu adalah bahwa kami dapat mengabstraksi mereka.

Saya telah membaca bahwa generik urutan pertama "abstract over types", sedangkan monads "abstract over type constructor". Dan kami juga melihat frasa seperti ini di kertas Pola Kue . Mengutip salah satu dari banyak contoh seperti itu:

Anggota tipe abstrak menyediakan cara yang fleksibel untuk mengabstraksi jenis komponen konkret.

Bahkan pertanyaan stack overflow yang relevan menggunakan terminologi ini. "tidak dapat secara eksistensial abstrak atas tipe berparameter ..."

Jadi ... apa sebenarnya arti "abstrak atas"?

Morgan Creighton
sumber

Jawaban:

124

Dalam aljabar, seperti dalam pembentukan konsep sehari-hari, abstraksi dibentuk dengan mengelompokkan hal-hal berdasarkan beberapa karakteristik esensial dan menghilangkan karakteristik spesifik lainnya. Abstraksi disatukan di bawah satu simbol atau kata yang menunjukkan kesamaan. Kami mengatakan bahwa kami mengabstraksikan perbedaan, tetapi ini benar-benar berarti kami terintegrasi oleh kesamaan.

Sebagai contoh, mempertimbangkan program yang mengambil jumlah dari angka 1, 2dan 3:

val sumOfOneTwoThree = 1 + 2 + 3

Program ini tidak terlalu menarik, karena tidak terlalu abstrak. Kita dapat mengabstraksi bilangan yang kita jumlahkan, dengan mengintegrasikan semua daftar bilangan di bawah satu simbol ns:

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

Dan kami juga tidak terlalu peduli bahwa itu adalah Daftar. List adalah konstruktor tipe tertentu (mengambil tipe dan mengembalikan tipe), tetapi kita bisa mengabstraksi konstruktor tipe dengan menentukan karakteristik penting mana yang kita inginkan (yang dapat dilipat):

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)

Dan kita dapat memiliki Foldablecontoh implisit untuk Listdan hal lain yang dapat kita lipat.

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

Terlebih lagi, kita dapat mengabstraksi operasi dan jenis operan:

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)

Sekarang kami memiliki sesuatu yang cukup umum. Metode ini mapReduceakan melipat apa pun F[A]yang kita dapat buktikan bahwa Fdapat dilipat dan itu Aadalah monoid atau dapat dipetakan menjadi satu. Sebagai contoh:

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)

Kami telah mengabstraksi monoid dan lipat.

Apocalisp
sumber
@coubeatczech Kode berjalan di REPL baik-baik saja. Versi Scala apa yang Anda gunakan, dan kesalahan apa yang Anda dapatkan?
Daniel C. Sobral
1
@Apocalisp Akan menarik jika Anda membuat salah satu dari dua contoh terakhir a Setatau jenis lipat lainnya. Contoh dengan Stringdan penggabungan juga akan cukup keren.
Daniel C. Sobral
1
Jawaban yang bagus, Runar. Terima kasih! Saya mengikuti saran Daniel, dan membuat setFoldable dan concatMonoid implisit, tanpa mengubah mapReduce sama sekali. Aku sedang dalam perjalanan untuk melakukan ini.
Morgan Creighton
6
Butuh beberapa saat bagi saya untuk mendapatkan bahwa di 2 baris terakhir Anda memanfaatkan fakta bahwa objek pendamping Sum dan Produk, karena mereka mendefinisikan apply (Int), diperlakukan sebagai Int => Sum dan Int => Product by the Scala penyusun. Sangat bagus!
Kris Nuttycombe
Posting yang bagus :)! Dalam contoh terakhir Anda, logika implisit Monoid tampaknya tidak diperlukan. Ini lebih sederhana: gist.github.com/cvogt/9716490
cvogt
11

Untuk perkiraan pertama, kemampuan untuk "mengabstraksikan" sesuatu berarti bahwa alih-alih menggunakan sesuatu itu secara langsung, Anda dapat membuat parameternya, atau menggunakannya "secara anonim".

Scala memungkinkan Anda untuk mengabstraksi tipe, dengan mengizinkan kelas, metode, dan nilai memiliki parameter tipe, dan nilai memiliki tipe abstrak (atau anonim).

Scala memungkinkan Anda untuk mengabstraksi tindakan, dengan mengizinkan metode memiliki parameter fungsi.

Scala memungkinkan Anda untuk mengabstraksi fitur, dengan mengizinkan tipe untuk didefinisikan secara struktural.

Scala memungkinkan Anda untuk mengabstraksi parameter tipe, dengan mengizinkan parameter tipe orde tinggi.

Scala memungkinkan Anda untuk mengabstraksi pola akses data, dengan memungkinkan Anda membuat ekstraktor.

Scala memungkinkan Anda untuk mengabstraksi "hal-hal yang dapat digunakan sebagai sesuatu yang lain", dengan mengizinkan konversi implisit sebagai parameter. Haskell melakukan hal yang sama dengan kelas tipe.

Scala tidak (belum) memungkinkan Anda untuk mengabstraksi kelas. Anda tidak bisa meneruskan kelas ke sesuatu, lalu menggunakan kelas itu untuk membuat objek baru. Bahasa lain mengizinkan abstraksi atas kelas.

("Monads abstrak atas konstruktor tipe" hanya benar dalam cara yang sangat terbatas. Jangan terpaku padanya sampai Anda memiliki momen "Aha! Saya mengerti monads !!".)

Kemampuan untuk mengabstraksi beberapa aspek komputasi pada dasarnya adalah yang memungkinkan penggunaan kembali kode, dan memungkinkan pembuatan pustaka fungsionalitas. Scala memungkinkan lebih banyak jenis hal untuk diabstraksi daripada bahasa yang lebih umum, dan perpustakaan di Scala dapat menjadi lebih kuat.

Dave Griffith
sumber
1
Anda bisa meneruskan Manifest, atau bahkan a Class, dan menggunakan refleksi untuk membuat instance objek baru dari kelas itu.
Daniel C. Sobral
6

Abstraksi adalah semacam generalisasi.

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

Tidak hanya di Scala tetapi banyak bahasa ada kebutuhan untuk memiliki mekanisme seperti itu untuk mengurangi kompleksitas (atau setidaknya membuat hierarki yang membagi informasi menjadi bagian yang lebih mudah dipahami).

Kelas adalah abstraksi atas tipe data sederhana. Ini seperti tipe dasar tetapi sebenarnya menggeneralisasikannya. Jadi kelas lebih dari sekedar tipe data sederhana tetapi memiliki banyak kesamaan dengannya.

Ketika dia mengatakan "mengabstraksi" yang dia maksud adalah proses yang Anda gunakan untuk menggeneralisasi. Jadi, jika Anda mengabstraksi metode sebagai parameter, Anda menggeneralisasi proses untuk melakukannya. misalnya, alih-alih meneruskan metode ke fungsi, Anda mungkin membuat beberapa jenis cara umum untuk menanganinya (seperti tidak meneruskan metode sama sekali tetapi membangun sistem khusus untuk menghadapinya).

Dalam hal ini yang dimaksud secara khusus adalah proses mengabstraksi masalah dan menciptakan solusi seperti oop untuk masalah tersebut. C memiliki kemampuan yang sangat sedikit untuk mengabstraksi (Anda dapat melakukannya tetapi menjadi sangat cepat berantakan dan bahasa tidak mendukungnya secara langsung). Jika Anda menulisnya dalam C ++, Anda dapat menggunakan konsep oop untuk mengurangi kompleksitas masalah (yah, kompleksitasnya sama tetapi konseptualisasi umumnya lebih mudah (setidaknya setelah Anda belajar berpikir dalam kerangka abstraksi)).

misalnya, Jika saya membutuhkan tipe data khusus yang seperti int tetapi, katakanlah dibatasi, saya dapat mengabstraksikannya dengan membuat tipe baru yang dapat digunakan seperti int tetapi memiliki properti yang saya butuhkan. Proses yang akan saya gunakan untuk melakukan hal seperti itu akan disebut "abstrak".

AbstrakDonansi
sumber
5

Ini adalah pertunjukan sempit saya dan tafsirkan interpretasi. Ini cukup jelas dan berjalan di 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
sumber
1
cukup banyak ide "abstrak atas" dalam kode- kuat namun singkat, akan mencoba bahasa ini +1
pengguna44298
2

Jawaban yang lain sudah memberikan gambaran yang baik tentang jenis abstraksi yang ada. Mari kita bahas kutipan satu per satu, dan berikan contoh:

Anda dapat mengirimkan metode (atau "fungsi") sebagai parameter, atau Anda dapat mengabstraksikannya. Anda dapat menentukan tipe sebagai parameter, atau Anda dapat mengabstraksikannya.

Lulus fungsi sebagai parameter: List(1,-2,3).map(math.abs(x))Jelas absdilewatkan sebagai parameter di sini. mapitu sendiri mengabstraksi fungsi yang melakukan hal khusus tertentu dengan setiap elemen daftar. val list = List[String]()menentukan tipe paramter (String). Anda bisa menulis jenis koleksi yang menggunakan anggota jenis abstrak sebagai gantinya: val buffer = Buffer{ type Elem=String }. Satu perbedaan adalah Anda harus menulis def f(lis:List[String])...tetapi def f(buffer:Buffer)..., jadi tipe elemennya agak "tersembunyi" dalam metode kedua.

Konsekuensi dari aliran acara kami menjadi nilai kelas satu adalah bahwa kami dapat mengabstraksikannya.

Dalam Swing, sebuah peristiwa "terjadi" begitu saja, dan Anda harus menanganinya di sini dan saat ini. Aliran acara memungkinkan Anda melakukan semua pemipaan kabel dengan cara yang lebih deklaratif. Misalnya, saat Anda ingin mengubah pendengar yang bertanggung jawab di Swing, Anda harus membatalkan pendaftaran yang lama dan mendaftarkan yang baru, dan mengetahui semua detail yang mengerikan (misalnya masalah threading). Dengan aliran acara, sumber acara menjadi sesuatu yang dapat Anda sebarkan begitu saja, membuatnya tidak jauh berbeda dari aliran byte atau char, oleh karena itu konsepnya lebih "abstrak".

Anggota tipe abstrak menyediakan cara yang fleksibel untuk mengabstraksi tipe komponen konkret.

Kelas Buffer di atas sudah menjadi contoh untuk ini.

Landei
sumber
1

Jawaban di atas memberikan penjelasan yang sangat bagus, tetapi untuk meringkasnya dalam satu kalimat, saya akan mengatakan:

Mengabstraksi sesuatu sama saja dengan mengabaikannya di tempat yang tidak relevan .

Michał Kaczanowicz
sumber