首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用高阶函数进行选项转换的Scala类型类

使用高阶函数进行选项转换的Scala类型类
EN

Stack Overflow用户
提问于 2016-08-25 19:04:27
回答 1查看 602关注 0票数 1

我有一些选项,当它们都不是的时候,我不想运行我的转换函数。

当前处理选项的方法如下所示:

代码语言:javascript
运行
复制
def writeOptionalXml[T](content: Option[T], mapFn: T => Xml): Xml =
  content match {
    case Some(c) => mapFn(c)
    case None => NodeSeq.Empty
}

它工作得很好。但是我还有其他不是选项但仍然可以为空的输入,例如一个空字符串、一个空xml节点或一些case类。

我认为这对我来说是一次很好的学习经历,可以重构成一个类型类。经过大量的代码讨论后,我发现我需要使用上下文边界来处理option类型,并认为我已经在实现类型类梦想的道路上走得很好了。

我遇到麻烦的地方是transform函数(在示例中,它的名字很糟糕,叫做mapFn)。在无选项的情况下,我需要方法签名:(content: OptionT,mapFn: t => Xml):Xml,而在其他情况下:(input: A,mapFn: a => Xml):Xml

我一直在努力改变类型签名,使用_试图得到我想要的东西,但没有用。

我目前拥有的代码的合成版本看起来像这样:

代码语言:javascript
运行
复制
import scala.annotation.implicitNotFound

object writableTypes extends App {

  type Xml = String
  @implicitNotFound("No member of type class in scope for ${T}")
  trait WritableLike[A] {
    def toXml[B](input: A, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml
  }

  object WritableLike {

    implicit object WritableLikeString extends WritableLike[String] {
      override def toXml[B](input: String, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml =
        mapFn(input)
    }

    implicit def OptionFormat[T: WritableLike]: Object = new WritableLike[Option[T]] {
      override def toXml[B](input: Option[T], mapFn: ((_$1) forSome {type _$1}) => Xml): Xml =
        mapFn(input.get)
    }

    def writeXml[X](input: X, mapFn: ((_$1) forSome {type _$1}) => Xml )(implicit ev: WritableLike[X]): Xml =
      ev.toXml[X](input, mapFn)
  }

  println(WritableLike.writeXml(Option(SomeCaseClass(5)), transformToXml))

  case class SomeCaseClass(content: Int) { def someMethod = ""}

  def transformToXml[T](input: SomeCaseClass): String = input.someMethod

}

不幸的是,这不能编译,因为在此方法中调用WritableLike.writeXml(Option(SomeCaseClass(5)),transformToXml)

函数transformToXml不满足所需的方法签名。

我已经尝试了这么多的排列,但找不到解决方案,否则很优雅。

我相信有一些简单的方法可以通过让所有东西都成为一个选项来解决它,但我更感兴趣的是找到让它真正通用的解决方案。

我不确定我是否已经很好地解释了这一点,这是我第一次尝试编写类型类,我以为它会很简单,但似乎我试图解决的特定问题有一些额外的复杂性。

如果有人对Scala类型系统的泛型编程有更深的理解,我将非常感激。

谢谢

EN

回答 1

Stack Overflow用户

发布于 2016-08-27 07:05:08

我认为您在普通函数中使用的type类中的mapFn有点问题。您可以构造您的类型类,使其仅依赖于输入数据。其余的工作是使用type类的实例完成的。以下是仅依赖于输入数据的WritableLike类型类定义的示例:

代码语言:javascript
运行
复制
trait WritableLike[A] {
  def toXml(input: A): String
}

在本例中,我使用String代替XML类型。使用这个定义,我们可以定义一些简单的类型类实例:

代码语言:javascript
运行
复制
implicit val stringWritableLike: WritableLike[String] = new WritableLike[String] {
  def toXml(input: String): String = s"<text>$input</text>"
}

implicit val intWritableLike: WritableLike[Int] = new WritableLike[Int] {
  def toXml(input: Int): String = s"<integer>$input</integer>"
}

在上面的代码中,我为StringInt类型定义了类型类实例。这两个实例都直接定义了如何将方法toXml的输入转换为XML,而不依赖于另一个函数来提供转换。这意味着,当我们将类型类应用于这两种类型中的任何一种时,无论值是什么,我们都将始终使用相同的转换。

如果我们想要重载某些值的转换,我们希望用其他类型包装这些值,并为该类型提供一个类型类实例。

代码语言:javascript
运行
复制
case class Name(name: String) extends AnyVal

implicit val nameWritableLike: WritableLike[Name] = new WritableLike[Name] {
  def toXml(input: Name): String = s"<name>${input.name}</name>"
}

在这里,我们定义了一个用于表示名称的case类和一个用于将名称转换为XML的特定类型类。

我将这些实例定义为值,但我也可能使用了defobject。重要的是,无论使用哪个关键字,实例都是隐式的。这种隐含性允许我们稍后获得这些实例,而无需显式地引用它们。

我们可以以Option为例:

代码语言:javascript
运行
复制
implicit def optionWritableLike[A](implicit instanceForA: WritableLike[A]): WritableLike[Option[A]] =
  new WritableLike[Option[A]] {
    def toXml(input: Option[A]): String = input match {
      case Some(a) => instanceForA.toXml(a)
      case None => "<empty />"
    }
  }

在上面的代码中,我们为Option类型定义了一个类型类实例,该实例作用于Option类型参数也具有类型类实例的所有值。在这里,我们将内部类型表示为函数类型参数A。由于我们不知道A是什么,我们需要以某种方式为该类型获取一个类型类实例。我们可以通过为我们需要的类型类实例添加一个隐式参数来实现这一点。最后,使用隐式实例,我们可以将底层值转换为XML。

类型类实例的隐式参数在该实例和该类型参数的实例之间创建一个依赖项。如果type参数的实例存在,我们可以使用依赖它的实例。如果没有,我们就不能使用该实例。

我们可以使用另一种语法来声明类型类依赖关系:

代码语言:javascript
运行
复制
implicit def optionWritableLike[A: WritableLike]: WritableLike[Option[A]] =
  new WritableLike[Option[A]] {
    def toXml(input: Option[A]): String = input match {
      case Some(a) => implicitly[WritableLike[A]].toXml(a)
      case None => "<empty />"
    }
  }

在这里,我们声明类型类依赖项作为类型参数定义的一部分。声明A: WritableLike意味着类型参数必须具有类型类WritableLike的类型类实例才能工作。我们可以使用Scala的关键字implicitly来访问实例。implicitly调用有点冗长,因此习惯上在类型类的伴生对象中定义帮助器函数:

代码语言:javascript
运行
复制
object WritableLike {
  def apply[A: WritableLike]: WritableLike[A] = implicitly[WritableLike[A]]
}

现在我们可以像这样访问类型类实例:WritableLike[A].toXml(a)

在实例定义之外使用类型类时,帮助器方法也会有所帮助:

代码语言:javascript
运行
复制
WritableLike[String].toXml("foobar")               // <text>foobar</text>
WritableLike[Int].toXml(123)                       // <integer>123</integer>
WritableLike[Name].toXml(Name("Alan"))             // <name>Alan</name>
WritableLike[Option[String]].toXml(Some("foobar")) // <text>foobar</text>
WritableLike[Option[Int]].toXml(None)              // <empty />
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39143569

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档