将可选转换函数应用于可选值时Scala编译器错误

时间:2013-01-18 19:38:20

标签: scala

我在Scala中有各种类型的值列表 - 字符串,整数,双数。其中一些值可能为空。我们可以将其视为List[Option[AnyRef]]。对于此列表中的每个值,如果存在,我想生成一个包含parens之间值的字符串;如果它不存在,那么只是一个空字符串。此外,在parens之间插入值之前,可能需要对其应用转换。例如,如果值为Option[String],我可能想要应用trim函数。

我已经通过将数据结构视为可选值的映射以及对该值进行操作的可选函数并返回一个字符串(我不知道如何在Scala中指定它)来解决这个问题。 Map [Option [A],Option [(A)=> String]]不正确,因为它表示每个选项键的类型相同)。这是我到目前为止用示例数据和函数编写的内容:

lazy val messageContext = Map(
  None -> None,
  Some("hello") -> None,
  Some(4) -> Some(negate _),
  None -> None
) map { t => wrapText(t._1, t._2)  } toList

private def wrapText[A](option: Option[A], transFn: Option[(A) => String]): String = option match {
  case Some(o) => " (%s)".format(transFn.getOrElse(stringIdentity _)(o))
  case _ => ""
}

private def stringIdentity[A](a: A) = a.toString

private def negate(n: Number) = n * -1

我收到了编译错误:

error: type mismatch;
 found   : Option[java.lang.Number => java.lang.String]
 required: Option[AnyVal{def getClass(): java.lang.Class[_ >: Double with Int <: AnyVal]} => String]
  ) map { t => wrapText(t._1, t._2)  } toList

我的方法是否接近这个? Scala是否可以轻松实现这一点,或者我是否必须使用一些scalaz魔法来使其真正起作用?

感谢。

编辑:上述输入的正确输出如下: List[""," (hello)"," (-4)",""]

2 个答案:

答案 0 :(得分:2)

不需要魔法。我不确定这是否是你想要的,但要重申

    输入中的
  • None表示输出中的""
  • 输入中的
  • Some(v)表示输出中的" (%s)".format(trans(v)),其中trans是值的可选转换

例如:

val input = List(None, Some("hello"), Some(4), None)

def trans(v: Any) = v match {
   case num @ 4 => -num
   case _       => v  // identity
}

val output = input.map {
   case Some(v) => " (%s)".format(trans(v))
   case _       => ""
}

答案 1 :(得分:0)

这里的第一个问题是Map[A,B]有两个类型参数用于整个地图,而不是每个tuple的几个类型参数。您不能使用Map来完成您的操作,因为Map将把键和值的最常见祖先作为类型参数:

scala>  import StackOverflowTest

StackOverflowTest.messageContext
import StackOverflowTest

scala> res0: scala.collection.immutable.Map[Option[java.lang.Comparable[_ >: java.lang.Integer with java.lang.String <: java.lang.Comparable[_ >: java.lang.Integer with java.lang.String <: java.lang.Comparable[_ >: java.lang.Integer with java.lang.String <: java.io.Serializable] with java.io.Serializable] with java.io.Serializable] with java.io.Serializable],Option[java.lang.Integer => Int]] = Map(None -> None, Some(hello) -> None, Some(4) -> Some(<function1>))

scala> 

当您将不同类型的元组放在地图或序列中时,您将丢失有关每个元组的类型信息,并且只保留第一个公共父元素。如果您需要一个能够保存每个Tuple2[A,B]具有不同A,B的元组映射的数据结构,则需要更加动态的结构,例如无形记录或HList。

https://github.com/milessabin/shapeless

这里的第二个问题是Number * 1的语义没有在Java中定义,你需要使用所需数字的子类型,例如java.lang.Integer。

第三个问题是你的wrapText需要transFn: Option[(A) => String]),而你的否定输出值类型不是String(实际上没有为java类数定义*)。如果您尝试仅编译以下内容,则删除Map部分并更正否定方法:

  private def wrapText[A](option: Option[A], transFn: Option[(A) => String]): String = option match {
    case Some(o) => " (%s)".format(transFn.getOrElse(stringIdentity _)(o))
    case _ => ""
  }

  private def stringIdentity[A](a: A) = a.toString

  private def negate(n: java.lang.Integer) = n * -1

  wrapText(Some(4),negate _ )

这不会编译错误:

error: type mismatch;
found   : Option[java.lang.Integer => Int]
required: Option[? => String]
wrapText(Some(4),Some(negate _) )

如果你最终纠正了否定以返回一个字符串,你将会进入最后一面

error: type mismatch;
found   : Option[java.lang.Integer => java.lang.String]
required: Option[Int => String]
wrapText(Some(4),Option(negate _) )

这里的问题是,当Scala和Java执行自动装箱和拆箱时,它们不会对类型参数执行自动转换:选项[Int =&gt; String]与Option [java.lang.Integer =&gt; String]不是同一个类,所以你必须强制第一个选项的类型才能正确编译它:

  wrapText(Some(4:java.lang.Integer),Some(negate _) )