web-dev-qa-db-ja.com

「abstract over」とはどういう意味ですか?

Scala文献では、「抽象化」というフレーズに遭遇することがよくありますが、意図がわかりません。

メソッド(または「関数」)をパラメーターとして渡すか、または抽象化できます。タイプをパラメーターとして指定するか、または抽象化できます。

別の例として、 "Deprecating the Observer Pattern" 論文では、

イベントストリームが一流の値であることによる結果は、それらを抽象化できることです。

モナドは「型コンストラクターを抽象化」しているのに対して、私はその最初の順序のジェネリックスを「型より抽象化」していることを読みました。 ケーキパターンペーパー にもこのようなフレーズがあります。多くのそのような例の1つを引用するには:

抽象型のメンバーは、コンポーネントの具体的な型を抽象化する柔軟な方法を提供します。

関連するスタックオーバーフローの質問でさえ、この用語を使用します。 "パラメータ化された型に対して存在的に抽象化することはできません..."

では、「抽象化」とは実際にはどういう意味ですか?

93

代数では、日常の概念形成と同様に、抽象化はいくつかの本質的な特性によって物事をグループ化し、それらの他の特定の特性を省略して形成されます。抽象化は、単一の記号または類似点を示すWordに統一されます。私たちはabstract overの違いだと言いますが、これは本当に類似性によってintegratingであることを意味します。

たとえば、12、および3の合計を取るプログラムを考えてみます。

val sumOfOneTwoThree = 1 + 2 + 3

このプログラムはあまり抽象的ではないため、あまり興味深いものではありません。 abstract over数値のすべてのリストを1つの記号nsの下に統合することで、合計する数値を次のようにできます。

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

そして、それがリストであることも特に気にしません。リストは特定の型コンストラクター(型を取り、型を返す)ですが、必要な特性(折りたたみ可能)を指定することでabstract over型コンストラクターを指定できます。

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

さらに、abstract over演算とオペランドのタイプの両方を使用できます。

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がモノイドであるか、または1つにマッピングできることを証明できれば、すべての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)

abstracted overモノイドと折りたたみ式があります。

121
Apocalisp

最初の近似では、何かを「抽象化」できるということは、その何かを直接使用する代わりに、そのパラメータを作成するか、そうでなければ「匿名で」使用できることを意味します。

Scalaを使用すると、クラス、メソッド、および値に型パラメーターを、値に抽象(または匿名)型を許可することで、型を抽象化できます。

Scalaでは、メソッドに関数パラメーターを持たせることで、アクションを抽象化できます。

Scalaでは、型を構造的に定義できるようにすることで、機能を抽象化できます。

Scalaでは、高次の型パラメーターを許可することで、型パラメーターを抽象化できます。

Scalaでは、エクストラクターを作成できるようにすることで、データアクセスパターンを抽象化できます。

Scalaでは、暗黙的な変換をパラメーターとして許可することにより、「他のものとして使用できるもの」を抽象化できます。 Haskellは型クラスでも同様に機能します。

Scalaでは、(まだ)クラスを抽象化することはできません。クラスを何かに渡し、そのクラスを使用して新しいオブジェクトを作成することはできません。他の言語では、クラスを抽象化できます。

(「モナドアブストラクトタイプコンストラクター」は、非常に限定的な方法でのみ当てはまります。「アハ!私はモナドを理解しました!!」の瞬間が来るまで、それに固執しないでください。)

計算の一部の側面を抽象化する機能は、基本的にコードの再利用を可能にし、機能のライブラリーの作成を可能にします。 Scalaを使用すると、主流の言語よりも多くの種類の事柄を抽象化できます。Scalaのライブラリは、それに応じてより強力になります。

11
Dave Griffith

抽象化は一種の一般化です。

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

Scalaだけでなく、多くの言語では、複雑さを軽減するためのこのようなメカニズムが必要です(または、少なくとも情報を理解しやすい部分に分割する階層を作成する)。

クラスは、単純なデータ型を抽象化したものです。基本的なタイプに似ていますが、実際には一般化されています。したがって、クラスは単純なデータ型以上のものですが、多くの共通点があります。

彼が「抽象化する」と言うとき、それはあなたが一般化するプロセスを意味します。したがって、メソッドをパラメータとして抽象化している場合は、それを行うプロセスを一般化しています。たとえば、メソッドを関数に渡す代わりに、それを処理するためのある種の一般化された方法を作成することができます(メソッドをまったく渡さないで、それに対処するための特別なシステムを構築するなど)。

この場合、彼は具体的には問題を抽象化し、問題のような解決策を作成するプロセスを意味します。 Cは抽象化する能力がほとんどありません(実行することはできますが、乱雑になり、言語が直接サポートしていません)。 C++で記述した場合は、oopの概念を使用して問題の複雑さを軽減できます(まあ、それは同じ複雑さですが、概念化は一般に簡単です(少なくとも抽象化の観点から考えることを学ぶと))。

たとえば、intのような特別なデータ型が必要であるが、制限付きと言えば、intのように使用でき、必要なプロパティを持つ新しい型を作成することで、それを抽象化できます。そのようなことをするために私が使用するプロセスは、「抽象化」と呼ばれます。

6

ここに私の狭いショーと解釈を示します。自明で、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()
5
huynhjl

他の答えは、どのような抽象化が存在するかについてすでに良い考えを与えています。引用を1つずつ確認して、例を示します。

メソッド(または「関数」)をパラメーターとして渡すことも、それらを抽象化することもできます。タイプをパラメーターとして指定することも、それらを抽象化することもできます。

関数をパラメーターとして渡す:List(1,-2,3).map(math.abs(x))明らかにabsがパラメーターとしてここに渡されます。 map自体は、各リスト要素で特定の特別なことを行う関数を抽象化します。 val list = List[String]()は、型パラメーター(String)を指定します。代わりに、抽象型メンバーを使用するコレクション型を書くことができます:_val buffer = Buffer{ type Elem=String }_。 1つの違いは、def f(lis:List[String])...def f(buffer:Buffer)...を記述する必要があることです。そのため、要素タイプは2番目のメソッドでは「非表示」の一種です。

イベントストリームが一流の値であることの結果は、それらを抽象化できることです。

Swingでは、イベントが突然「発生」し、ここで今すぐ対処する必要があります。イベントストリームを使用すると、すべての配管をより宣言的な方法で行うことができます。例えば。 Swingで責任のあるリスナーを変更する場合は、古いリスナーの登録を解除し、新しいリスナーを登録して、すべての残酷な詳細(スレッドの問題など)を知る必要があります。イベントストリームでは、イベントのsourceは単純に渡すことができるものになり、バイトストリームや文字ストリームとそれほど変わらないため、より「抽象的な」概念になります。

抽象型メンバーは、具象型のコンポーネントを抽象化する柔軟な方法を提供します。

上記のBufferクラスはすでにこの例です。

2
Landei

上記の回答は優れた説明を提供しますが、1つの文に要約すると、次のようになります。

何かを抽象化する無関係な場所で無視すると同じです。

0