Scala类型类说明:实现String.read函数

在这篇简短的文章中,我想解释一下如何在Scala中使用typeclass模式来实现即席多态性。 在本文中,我们不会过多讨论理论上的细节,我要向您展示的主要是,创建一个灵活的可扩展模型确实很容易,而不必将您的域模型与特定的实现或特性绑定在一起。 Internet上有很多有关Scala类型类的资源,但是我找不到很好的参考,所以这就是为什么我创建了这个。 在此示例中,我们将看一下Haskell的read类型类的一种非常务实的(并且是幼稚的)实现。 该类型类允许您以通用方式将字符串转换为特定类型。

我们要使用此类型类完成的操作如下(完整的示例可以在这里找到: https : //gist.github.com/josdirksen/9051baf09003dac37386

println(Readable[Double].read("10"))
  println(Readable[Int].read("10"))
  println(Readable[String].read("Well duh!"))
  println(Readable[List[Char]].read("Well duh!"))
  println(Readable[List[String]].read("Using:A:Separator:to:split:a:String"))

  // we can also use the read function directly
  println("20".read[Double]);
  println("Using:A:Separator:to:split:a:String".read[List[Char]]);
  println("Using:A:Separator:to:split:a:String".read[List[String]]);
  println(Readable[Task].read("10|Title Text|Title Content"))
  println("20|Another title Text|Another title Content".read[Task])

使用Readable类型类,我们提供了一种将String转换为特定类型的通用方法。 在上面的示例中,我们使用类型类将String转换为一些基本类型,还转换为不同的列表和特定的案例类。 基本类型的功能并不是真的有用,因为scala String对象已经提供了toDouble,toString等函数。 但是,通过这种方式,您不需要知道要调用的特定函数,而只需指定所需的目标类型即可:)但是,与在此处看到的Task案例类相比,它变得更加有趣。 正如在其余代码中所看到的,通过使用隐式,我们可以简单地添加对此case类的支持,而不必更改String类或Task类的实现。

要实现类型类,我们首先必须定义特征,该特征定义了我们需要实现的功能。 在此示例中,我们只有一个功能:

/**
   * The readable trait defines how objects can be converted from a string
   * representation to the objects instance. For most of the standard types
   * we can simply use the toType function of String.
   */
  trait Readable[T] {
    def read(x: String): T
  }

read函数应该将String转换为指定的类型T。现在,我们已经定义了特征,让我们看一下伴随对象,该对象包含一些帮助器类和简单的实现:

/**
   * Companion object containing helper functions and standard implementations
   */
  object Readable {

    /**
     * Helper function which allows creation of Readable instances
     */
    def toReadable[T](p: String => T): Readable[T] = new Readable[T] {
      def read(x: String): T = p(x)
    }

    /**
     * Allow for construction of standalone readables, if the ops aren't used
     */
    def apply[A](implicit instance: Readable[A]): Readable[A] = instance

    // Using the toReadable creates cleaner code, we could also explicitly
    // define the implicit instances:
    //
    //   implicit object ReadableDouble extends Readable[Double] {
    //      def read(s: String): Double = s.toDouble
    //    }
    //    implicit object ReadableInt extends Readable[Int] {
    //      def read(s: String): Int = s.toInt
    //    }
    implicit val ReadableDouble = toReadable[Double](_.toDouble)
    implicit val ReadableInt = toReadable[Int](_.toInt)
    implicit val ReadableLong = toReadable[Long](_.toLong)
    implicit val ReadableString = toReadable[String](new String(_))
    implicit val ReadableBoolean = toReadable[Boolean](_.toBoolean)
    implicit val ReadableCharList = toReadable[List[Char]](_.toCharArray.toList)
    implicit val ReadableStringList = toReadable[List[String]](_.split(':').toList)

  }

代码应该不难理解。 在这里,我们要做的是创建许多Readable实现。 重要的是要注意,我们使用了隐式参数,以便以后可以将它们拉入范围。 至此,我们已经可以开始使用typeclass了。 为此,我们导入隐式对象并使用Readable类型类,如下所示:

import Readable._
  import Readable.ops._

  // now we can just get an instance of a readable and call the read function
  // to parse a string to a specific type.
  println(Readable[Double].read("10"))
  println(Readable[Int].read("10"))
  println(Readable[String].read("Well duh!"))
  println(Readable[List[Char]].read("Well duh!"))
  println(Readable[List[String]].read("Using:A:Separator:to:split:a:String"))

容易吧? Scala将查找与指定类型匹配的隐式并将String转换为该类型。 很好,但是看起来并不那么好。 我们需要实例化一个Readable(即使不是太多的代码)并调用read来转换String。 我们可以通过使用为我们进行转换的read函数扩展String类来使其变得更加容易。 为此,将以下内容添加到Readable随播对象。

/**
     * Extend the string object with a read function.
     */
    object ops {
      implicit class pp[T](s: String) {

        /**
         * The type parameter should have an implcit Readable in scope. Use
         * implicitly to access it and call the read function
         */
        def read[T: Readable]= implicitly[Readable[T]].read(s)
      }
    }

这些操作定义了一个隐式转换,该转换将读取函数添加到String对象。 剩下的就是导入ops隐式函数,我们可以直接使用read:

import Readable.ops._
  // we can also use the read function directly
  println("20".read[Double]);
  println("Using:A:Separator:to:split:a:String".read[List[Char]]);
  println("Using:A:Separator:to:split:a:String".read[List[String]]);

同样的方法也可以用于为案例类创建读取函数:

// creating custom read function can be done without tying a case class
  // to the reads implementation. In the following sample assume we
  // serialize it to a string with | as separators:
  //     10|Title Text|Title Content
  case class Task(id: Long, title: String, content: String)

  // simple convert the incoming string to a Task
  implicit val readableTask = toReadable(
    _.split('|') match {
      case Array(id: String, title: String, content: String) => new Task(id.read[Long], title, content)
    }
  )

  println(Readable[Task].read("10|Title Text|Title Content"))
  println("20|Another title Text|Another title Content".read[Task])

就是这样。 通过仅实现Readable [Task]特性,可以以与其他对象相同的方式处理自定义Task案例类。

翻译自: https://www.javacodegeeks.com/2015/09/scala-typeclass-explained-implement-a-string-read-function.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值