【问题标题】:Scala - Typeclass example. How to explain the benefits?Scala - 类型类示例。如何解释好处?
【发布时间】:2012-12-07 10:27:45
【问题描述】:

所以我向同事/朋友展示了 Scala 中类型类模式的示例。它看起来像这样:

case class Song(name: String, artist: String)
case class Address(street: String, number: Int)

trait LabelMaker[T] {
  def output(t: T): String
}

object LabelMaker {
  implicit object addressLabelMaker extends LabelMaker[Address] {
    def output(address: Address) = {
      address.number + " " + address.street + " street"
    }
  }
  implicit object songLabelMaker extends LabelMaker[Song] {
    def output(song: Song) = {
      song.artist + " - " + song.name
    }
  }
  def label[T : LabelMaker](t: T) = implicitly[LabelMaker[T]].output(t)
}

可以这样使用:

import LabelMaker._
println(label(new Song("Hey Ya", "Outkast"))) // Outkast - Hey Ya
println(label(new Address("Smithsonian", 273))) // 273 Smithsonian street

这不是最好的例子,回想起来我希望我能想出一个更好的例子。在向他展示后,他以一个反例作为回应,并询问 typeclass 模式实际上带来了什么好处:

case class Song(name: String, artist: String)
case class Address(street: String, number: Int)

object LabelMaker {

    def label(address: Address) = {
        address.number + " " + address.street + " street"
    }

    def label(song: Song) = {
        song.artist + " - " + song.name
    }
}

import LabelMaker._
println(label(new Song("Hey Ya", "Outkast"))) // Outkast - Hey Ya
println(label(new Address("Smithsonian", 273))) // 273 Smithsonian street

我很难正确回答这个问题,这让我意识到我不太了解 100% 的收益。当其他人使用它们时,我理解它们的实现和非常本地化的好处,但实际上简洁地解释它们是相当困难的。谁能帮我?也许扩展我的例子来真正展示好处。

【问题讨论】:

    标签: scala functional-programming typeclass


    【解决方案1】:

    类型类捕获retroactive extensibility 的概念。使用静态方法重载,您必须在一个地方一次定义它们,但是使用类型类,您可以随时为任何模块中的任何新类型定义新实例。 例如

    object LabelMaker {
      // ... your original cases here ...
      def label[T : LabelMaker](t: T) = implicitly[LabelMaker[T]].output(t)
    }
    
    // somewhere in future
    object SomeModule {
      import LabelMaker._
    
      case class Car(title: String)
    
      implicit object carLabelMaker extends LabelMaker[Car] {
        def output(car: Car) = car.title
      }  
    }
    
    object Main extends App {
      import LabelMaker._
      import SomeModule._
    
      println(label(Car("Mustang")))
    }
    

    【讨论】:

    • 这与继承反例的LabelMaker对象以支持Car类有什么不同?
    • T 类型参数必须是案例类吗?
    • 一旦编写了类,要使其继承自某个类或特征,您必须触及它的定义,这可能对您不可用(例如,它是某个库中的类)。使用类型类,您不必触摸类定义来为该类编写实例。这是提到的追溯可扩展性的方面之一。不,T 类型参数的规则与常规泛型类的规则相同。
    【解决方案2】:

    类型推断和类型类的组合:

    implicit def tupleLabel[A: LabelMaker,B: LabelMaker] = new LabelMaker[(A,B)]{
      def output(tuple: (A,B)) = 
        implicitly[Label[A]].label(tuple._1) + " and " + implicitly[Label[B]].label(tuple._2)
    }
    

    这显然很有用,但不适用于您同事的 Java 版本。

    【讨论】:

    • 这个问题的想法是明确解释“明显有用”的位:P
    猜你喜欢
    • 2015-06-03
    • 2015-05-03
    • 1970-01-01
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    • 2023-02-14
    • 2020-08-05
    • 2013-01-05
    相关资源
    最近更新 更多