"추상 오버"는 무엇을 의미합니까?


95

종종 Scala 문헌에서 "abstract over"라는 문구를 접하지만 의도를 이해하지 못합니다. 예를 들어 Martin Odersky는 다음과 같이 씁니다.

메서드 (또는 "함수")를 매개 변수로 전달하거나 추상화 할 수 있습니다. 유형을 매개 변수로 지정하거나 그 위에 추상화 할 수 있습니다.

또 다른 예로, "관찰자 패턴 사용 중단" 문서에서

이벤트 스트림이 일류 값인 결과는 그것들을 추상화 할 수 있다는 것입니다.

필자는 1 차 제네릭 "추상 형식"을 읽은 반면 모나드는 "형식 생성자에 대한 개요"를 읽었습니다. 그리고 우리는 또한 Cake Pattern 논문 에서 이와 같은 문구를 볼 수 있습니다 . 이러한 많은 예 중 하나를 인용하려면 :

추상 유형 멤버는 구체적인 유형의 구성 요소 를 추상화 하는 유연한 방법을 제공 합니다.

관련 스택 오버플로 질문도이 용어를 사용합니다. "매개 변수화 된 유형에 대해 실존 적으로 추상화 할 수 없습니다 ..."

그래서 .. "추상 오버"는 실제로 무엇을 의미합니까?

답변:


124

대수학에서는 일상적인 개념 형성에서와 마찬가지로 몇 가지 필수 특성으로 사물을 그룹화하고 특정 다른 특성을 생략하여 추상화가 형성됩니다. 추상화는 유사성을 나타내는 단일 기호 또는 단어로 통합됩니다. 우리 는 차이점 을 추상화 한다고 말하지만 이것은 실제로 우리가 유사점으로 통합 된다는 것을 의미 합니다.

예를 들어, 숫자의 합을 취하는 프로그램을 고려 1, 23:

val sumOfOneTwoThree = 1 + 2 + 3

이 프로그램은 매우 추상적이지 않기 때문에 그다지 흥미롭지 않습니다. 단일 기호 아래에 모든 숫자 목록을 통합하여 합산하는 숫자를 추상화 할 수 있습니다 ns.

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

그리고 우리는 그것이 List라는 것도 특별히 신경 쓰지 않습니다. List는 특정 유형 생성자 (유형을 취하고 유형을 반환)이지만 원하는 필수 특성 (접을 수 있음)을 지정하여 유형 생성자를 추상화 할 수 있습니다.

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 2011 년

1
@Apocalisp 두 개의 최종 예제 중 하나를 a Set또는 다른 접이식 유형으로 만들면 흥미로울 것입니다 . a String와 연결을 사용한 예도 꽤 멋질 것입니다.
Daniel C. Sobral

1
아름다운 대답, Runar. 감사! Daniel의 제안에 따라 mapReduce를 전혀 변경하지 않고 암시 적 setFoldable 및 concatMonoid를 만들었습니다. 나는 이것을 괴롭히는 길에 잘 가고있다.
Morgan Creighton 2011 년

6
마지막 두 줄에서 Sum 및 Product 컴패니언 개체가 apply (Int)를 정의하기 때문에 Scala에서 Int => Sum 및 Int => Product로 처리된다는 사실을 활용하는 데 잠시 시간이 걸렸습니다. 컴파일러. 아주 좋아요!
Kris Nuttycombe 2011 년

좋은 게시물 :)! 마지막 예에서 Monoid 암시 적 논리는 불필요 해 보입니다. 더 간단합니다 : gist.github.com/cvogt/9716490
cvogt

11

첫 번째 근사치로, 무언가를 "추상화"할 수 있다는 것은 무언가를 직접 사용하는 대신 매개 변수를 만들거나 그렇지 않으면 "익명으로"사용할 수 있음을 의미합니다.

Scala를 사용하면 클래스, 메서드 및 값에 유형 매개 변수를 허용하고 값에 추상 (또는 익명) 유형을 허용하여 유형을 추상화 할 수 있습니다.

Scala를 사용하면 메서드가 함수 매개 변수를 갖도록 허용하여 작업을 추상화 할 수 있습니다.

Scala를 사용하면 유형을 구조적으로 정의하여 기능을 추상화 할 수 있습니다.

Scala를 사용하면 고차 유형 매개 변수를 허용하여 유형 매개 변수를 추상화 할 수 있습니다.

Scala를 사용하면 추출기를 만들 수 있으므로 데이터 액세스 패턴을 추상화 할 수 있습니다.

Scala를 사용하면 암시 적 변환을 매개 변수로 허용하여 "다른 것으로 사용할 수있는 것"을 추상화 할 수 있습니다. Haskell은 유형 클래스와 유사하게 수행합니다.

Scala는 (아직) 클래스를 추상화하는 것을 허용하지 않습니다. 클래스를 무언가에 전달한 다음 해당 클래스를 사용하여 새 개체를 만들 수 없습니다. 다른 언어는 클래스에 대한 추상화를 허용합니다.

( "모나드는 유형 생성자에 대한 추상화"는 매우 제한적인 방식으로 만 적용됩니다. "아하! 모나드를 이해합니다 !!"순간이 나올 때까지 끊지 마십시오.)

계산의 일부 측면을 추상화하는 기능은 기본적으로 코드 재사용을 허용하고 기능 라이브러리 생성을 가능하게합니다. Scala를 사용하면 더 많은 주류 언어보다 더 많은 종류의 사물을 추상화 할 수 있으며 Scala의 라이브러리는 그에 따라 더 강력 할 수 있습니다.


1
당신은 전달할 수 있습니다 Manifest, 심지어 Class, 사용 반사 해당 클래스의 새로운 객체를 생성 할 수 있습니다.
Daniel C. Sobral 2011 년

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]()유형 매개 변수 (문자열)를 지정합니다. 대신 추상 유형 멤버를 사용하는 컬렉션 유형을 작성할 수 val buffer = Buffer{ type Elem=String }있습니다.. 한 가지 차이점은 글을 써야 def f(lis:List[String])...하지만def f(buffer:Buffer)... 요소 유형 종류 두 번째 방법으로 "숨겨진"입니다, 그래서.

이벤트 스트림이 일류 값인 결과는 그것들을 추상화 할 수 있다는 것입니다.

Swing에서 이벤트는 갑자기 "일어남"에 불과하며 지금 여기서 처리해야합니다. 이벤트 스트림을 사용하면보다 선언적인 방식으로 모든 연결 작업을 수행 할 수 있습니다. 예를 들어 Swing에서 책임있는 리스너를 변경하려면 이전 등록을 취소하고 새 리스너를 등록해야하며 모든 세부 사항 (예 : 스레딩 문제)을 알아야합니다. 이벤트 스트림을 사용하면 이벤트 소스 가 간단히 전달할 수있는 것이되어 바이트 또는 문자 스트림과 크게 다르지 않으므로보다 "추상적 인"개념이됩니다.

추상 유형 멤버는 구체적인 유형의 구성 요소를 추상화하는 유연한 방법을 제공합니다.

위의 Buffer 클래스는 이미 이에 대한 예입니다.


1

위의 답변은 훌륭한 설명을 제공하지만 한 문장으로 요약하면 다음과 같습니다.

무언가를 추상화하는 것은 무관 한 곳에서 그것을 무시하는 것과 매우 동일 합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.