What does “abstract over” mean?

后端 未结 6 2024
醉话见心
醉话见心 2021-01-29 17:58

Often in the Scala literature, I encounter the phrase \"abstract over\", but I don\'t understand the intent. For example, Martin Odersky writes

You can p

6条回答
  •  星月不相逢
    2021-01-29 18:30

    In algebra, as in everyday concept formation, abstractions are formed by grouping things by some essential characteristics and omitting their specific other characteristics. The abstraction is unified under a single symbol or word denoting the similarities. We say that we abstract over the differences, but this really means we're integrating by the similarities.

    For example, consider a program that takes the sum of the numbers 1, 2, and 3:

    val sumOfOneTwoThree = 1 + 2 + 3
    

    This program is not very interesting, since it's not very abstract. We can abstract over the numbers we're summing, by integrating all lists of numbers under a single symbol ns:

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

    And we don't particularly care that it's a List either. List is a specific type constructor (takes a type and returns a type), but we can abstract over the type constructor by specifying which essential characteristic we want (that it can be folded):

    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)
    

    And we can have implicit Foldable instances for List and any other thing we can fold.

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

    What's more, we can abstract over both the operation and the type of the operands:

    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)
    

    Now we have something quite general. The method mapReduce will fold any F[A] given that we can prove that F is foldable and that A is a monoid or can be mapped into one. For example:

    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)
    

    We have abstracted over monoids and foldables.

提交回复
热议问题