"Soyut bitti" ne anlama geliyor?


95

Scala literatüründe sık sık "soyut bitti" cümlesiyle karşılaşıyorum, ancak niyeti anlamıyorum. Örneğin , Martin Odersky yazıyor

Yöntemleri (veya "işlevleri") parametre olarak geçirebilir veya üzerlerinden soyutlayabilirsiniz . Türleri parametre olarak belirleyebilir veya bunların üzerinden soyutlayabilirsiniz .

Başka bir örnek olarak, "Gözlemci Modelinin Kullanımdan Kaldırılması" makalesinde,

Olay akışlarımızın birinci sınıf değerler olmasının bir sonucu, onları soyutlayabilmemizdir .

Birinci dereceden jenerikleri "türler üzerinde soyut", monadlar ise "tür oluşturuculara göre soyut" okudum. Ve Cake Pattern kağıdında buna benzer ifadeler görüyoruz . Bu tür birçok örnekten birini alıntılamak gerekirse:

Soyut tip üyeler, somut bileşen türleri üzerinde soyutlama yapmak için esnek bir yol sağlar .

İlgili yığın taşması soruları bile bu terminolojiyi kullanır. "parametreleştirilmiş tür üzerinde varoluşsal olarak soyut olamaz ..."

Öyleyse ... "soyut bitti" aslında ne anlama geliyor?

Yanıtlar:


124

Cebirde, günlük kavram oluşumunda olduğu gibi, soyutlamalar, nesneleri bazı temel özelliklere göre gruplandırarak ve onların belirli diğer özelliklerini çıkararak oluşturulur. Soyutlama, benzerlikleri ifade eden tek bir sembol veya kelime altında birleştirilir. Farklılıkları soyutladığımızı söylüyoruz , ancak bu gerçekten benzerliklerle bütünleştiğimiz anlamına geliyor .

Örneğin, sayıların toplamını alır bir program düşünün 1, 2ve 3:

val sumOfOneTwoThree = 1 + 2 + 3

Bu program, çok soyut olmadığı için çok ilginç değil. Biz can üzerinde soyut bir tek sembolü altında sayıların tüm listeleri entegre ederek, toplayarak konum numaraları ns:

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

Ve bunun bir Liste olması da umurumuzda değil. Liste, belirli bir tür kurucusudur (bir tür alır ve bir tür döndürür), ancak hangi temel özelliği istediğimizi (katlanabileceğini) belirleyerek tür oluşturucuyu soyutlayabiliriz :

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)

Ve katlayabileceğimiz herhangi bir şey Foldableiçin örtük örneklerimiz Listolabilir.

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

Dahası , işlenenlerin hem işleyişi hem de türü hakkında özetleyebiliriz :

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)

Şimdi oldukça genel bir şeyimiz var. Yöntem mapReduce, bunun katlanabilir olduğunu ve bunun bir monoid olduğunu veya bir tanede eşlenebileceğini F[A]kanıtlayabildiğimiz herhangi bir veriyi katlayacaktır. Örneğin:FA

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)

Biz var üzerinde soyutlanmış Monoidler ve foldables.


@coubeatczech Kod, REPL cezasında çalışır. Scala'nın hangi sürümünü kullanıyorsunuz ve hangi hatayı aldınız?
Daniel C. Sobral

1
@Apocalisp Son iki örnekten birini a Setveya başka bir katlanabilir tipte yaparsanız ilginç olurdu . Bir Stringve birleştirilmiş bir örnek de oldukça havalı olurdu.
Daniel C. Sobral

1
Güzel cevap Runar. Teşekkürler! Daniel'in önerisini takip ettim ve mapReduce'u hiç değiştirmeden örtük setFoldable ve concatMonoid'i yarattım. Ben bu işin peşindeyim.
Morgan Creighton

6
Son 2 satırda Sum ve Product yardımcı nesnelerinin, apply (Int) 'yı tanımladıkları için Scala tarafından Int => Sum ve Int => Product olarak ele alındığı gerçeğinden yararlandığınızı anlamak biraz zaman aldı. derleyici. Çok hoş!
Kris Nuttycombe

Güzel mesaj :)! Son örneğinizde, Monoid örtük mantığı gereksiz görünüyor. Bu daha basit: gist.github.com/cvogt/9716490
cvogt

11

İlk yaklaşıma göre, bir şeyi "soyutlayabilmek", o şeyi doğrudan kullanmak yerine onun bir parametresini oluşturabileceğiniz veya başka bir şekilde "anonim" olarak kullanabileceğiniz anlamına gelir.

Scala, sınıfların, yöntemlerin ve değerlerin tür parametrelerine ve değerlerin soyut (veya anonim) türlere sahip olmasına izin vererek türleri soyutlamanıza olanak tanır.

Scala, yöntemlerin işlev parametrelerine sahip olmasına izin vererek eylemleri soyutlamanıza izin verir.

Scala, türlerin yapısal olarak tanımlanmasına izin vererek özellikler üzerinde soyutlama yapmanızı sağlar.

Scala, daha yüksek dereceli tip parametrelere izin vererek aşırı tip parametrelerini soyutlamanıza izin verir.

Scala, çıkarıcılar oluşturmanıza izin vererek veri erişim kalıpları üzerinde soyutlama yapmanızı sağlar.

Scala, parametreler olarak örtük dönüşümlere izin vererek "başka bir şey olarak kullanılabilen şeyler" üzerinde soyutlama yapmanıza olanak tanır. Haskell, tür sınıflarıyla benzer şekilde çalışır.

Scala (henüz) sınıfları soyutlamanıza izin vermiyor. Bir sınıfı bir şeye geçiremezsiniz ve sonra bu sınıfı yeni nesneler oluşturmak için kullanamazsınız. Diğer diller, sınıflar üzerinde soyutlamaya izin verir.

("Monadların tür oluşturuculara göre soyutlanması" yalnızca çok kısıtlayıcı bir şekilde doğrudur. "Aha! Monadları anlıyorum !!" anınızı yakalayana kadar takılmayın.)

Hesaplamanın bazı yönleri üzerinde soyutlama yeteneği, temelde kodun yeniden kullanımına izin veren ve işlevsellik kitaplıklarının oluşturulmasını sağlayan şeydir. Scala, ana akım dillerden çok daha fazla şeyin soyutlanmasına izin verir ve Scala'daki kütüphaneler buna bağlı olarak daha güçlü olabilir.


1
A Manifest, hatta a geçebilir Classve o sınıfın yeni nesnelerini somutlaştırmak için yansıma kullanabilirsiniz.
Daniel C. Sobral

6

Soyutlama, bir tür genellemedir.

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

Sadece Scala'da değil, birçok dilde karmaşıklığı azaltmak için bu tür mekanizmalara ihtiyaç vardır (veya en azından bilgileri anlaşılması daha kolay parçalara bölen bir hiyerarşi yaratmak).

Bir sınıf, basit bir veri türü üzerinden yapılan bir soyutlamadır. Temel bir tür gibi ama aslında onları genelleştiriyor. Yani bir sınıf, basit bir veri türünden daha fazlasıdır, ancak onunla ortak birçok şeye sahiptir.

"Soyutlama" dediği zaman, genelleme yaptığınız süreci kastediyor. Dolayısıyla, parametreler olarak yöntemler üzerinde soyutlama yapıyorsanız, bunu yapma sürecini genellemiş olursunuz. Örneğin, yöntemleri işlevlere geçirmek yerine, onu ele almak için bir tür genelleştirilmiş yol yaratabilirsiniz (örneğin, yöntemleri hiç iletmemek, ancak bununla başa çıkmak için özel bir sistem oluşturmak gibi).

Bu durumda, özellikle bir problemi soyutlama ve soruna bir oop benzeri çözüm yaratma sürecini kastediyor. C'nin soyutlama yeteneği çok azdır (bunu yapabilirsiniz, ancak çok hızlı bir şekilde dağınık hale gelir ve dil bunu doğrudan desteklemez). C ++ ile yazdıysanız, problemin karmaşıklığını azaltmak için oop kavramlarını kullanabilirsiniz (iyi, aynı karmaşıklıktır ancak kavramsallaştırma genellikle daha kolaydır (en azından soyutlamalar açısından düşünmeyi öğrendiğinizde)).

Örneğin, int gibi ama kısıtlı olan özel bir veri türüne ihtiyacım olsaydı, int gibi kullanılabilen ancak ihtiyacım olan özelliklere sahip yeni bir tür oluşturarak bunun üzerinden soyutlayabilirdim. Böyle bir şey yapmak için kullanacağım sürece "soyutlama" denirdi.


5

İşte dar gösteri ve anlatım yorumum. Kendinden açıklamalıdır ve REPL'de çalışır.

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

2

Diğer cevaplar, ne tür soyutlamaların var olduğuna dair iyi bir fikir veriyor. Alıntıları tek tek inceleyelim ve bir örnek verelim:

Yöntemleri (veya "işlevleri") parametre olarak geçirebilir veya üzerlerinden soyutlayabilirsiniz. Türleri parametre olarak belirleyebilir veya bunların üzerinden soyutlayabilirsiniz.

Fonksiyonu parametre olarak geçirin: Burada List(1,-2,3).map(math.abs(x))açıkça parametre absolarak geçilir. mapher liste öğesi ile belirli bir özel şey yapan bir işlevin kendisi özetler. val list = List[String]()bir tür parametresini (Dize) belirtir. Bunun yerine soyut tip üyelerini kullanan bir koleksiyon türü yazabilirsiniz: val buffer = Buffer{ type Elem=String }. Farklı olarak yazmak zorunda olduğunu def f(lis:List[String])...ancak def f(buffer:Buffer)...eleman tipi tür ikinci yöntemde "gizli" bir yani.

Olay akışlarımızın birinci sınıf değerler olmasının bir sonucu, onları soyutlayabilmemizdir.

Swing'de bir olay birdenbire "olur" ve bununla burada ve şimdi ilgilenmeniz gerekir. Olay akışları, bir kablolamanın tüm tesisatını daha açıklayıcı bir şekilde yapmanızı sağlar. Örneğin, Swing'de sorumlu dinleyiciyi değiştirmek istediğinizde, eskisinin kaydını silmeniz ve yenisini kaydetmeniz ve tüm kanlı ayrıntıları bilmeniz gerekir (örn. İş parçacığı sorunları). Olay akışları ile olayların kaynağı , bir bayt veya karakter akışından çok da farklı olmayan, dolayısıyla daha "soyut" bir kavram olan, basitçe aktarabileceğiniz bir şey haline gelir.

Soyut tip üyeler, somut bileşen türleri üzerinde soyutlama yapmak için esnek bir yol sağlar.

Yukarıdaki Buffer sınıfı buna zaten bir örnektir.


1

Yukarıdaki cevaplar mükemmel bir açıklama sağlar, ancak bunu tek bir cümleyle özetlemek için şunu söyleyebilirim:

Bir şey üzerinde soyutlamak , alakasız olduğu yerde onu ihmal etmekle aynı şeydir .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.