在 Scala 元组上使用功能组合器?

Posted

技术标签:

【中文标题】在 Scala 元组上使用功能组合器?【英文标题】:Use functional combinators on Scala Tuples? 【发布时间】:2011-01-21 08:15:09 【问题描述】:

'map' 保留了元素的数量,因此在元组上使用它似乎是明智的。

到目前为止我的尝试:

scala> (3,4).map(_*2)    
error: value map is not a member of (Int, Int)
       (3,4).map(_*2)
             ^
scala> (3,4).productIterator.map(_*2)
error: value * is not a member of Any
       (3,4).productIterator.map(_*2)
                                  ^
scala> (3,4).productIterator.map(_.asInstanceOf[Int]*2)
res4: Iterator[Int] = non-empty iterator

scala> (3,4).productIterator.map(_.asInstanceOf[Int]*2).toList
res5: List[Int] = List(6, 8)

看起来很痛苦...而且我什至还没有开始尝试将其转换回元组。 我做错了吗?图书馆可以改进吗?

【问题讨论】:

看起来你正在使用一个元组,而你确实应该使用一个集合。考虑改用真正的集合类 - 元组不应该用作一种集合。 @Jesper:我不同意:我可能只是想干脆地将相同的操作应用到静态已知大小的项目集合上。 【参考方案1】:

一般来说,一个元组的元素类型是不一样的,所以map没有意义。不过,您可以定义一个函数来处理特殊情况:

scala> def map[A, B](as: (A, A))(f: A => B) = 
     as match  case (a1, a2) => (f(a1), f(a2))  
map: [A,B](as: (A, A))(f: (A) => B)(B, B)

scala> val p = (1, 2)    
p: (Int, Int) = (1,2)

scala> map(p) _ * 2 
res1: (Int, Int) = (2,4)

您可以使用 Pimp My Library 模式将其称为 p.map(_ * 2)

更新

即使元素的类型不相同,Tuple2[A, B] 也是一个Bifunctor,可以通过bimap 操作进行映射。

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> val f = (_: Int) * 2
f: (Int) => Int = <function1>

scala> val g = (_: String) * 2
g: (String) => String = <function1>

scala> f <-: (1, "1") :-> g
res12: (Int, String) = (2,11)

更新 2

http://gist.github.com/454818

【讨论】:

作为记录,我正在将接受的答案从这个更改为关于 Shapeless 库的最新答案,该答案在 2010 年不可用。【参考方案2】:

shapeless 支持通过中介 HList 表示映射和折叠元组,

REPL 会话示例,

scala> import shapeless._ ; import Tuples._
import shapeless._
import Tuples._

scala> object double extends (Int -> Int) (_*2)
defined module double

scala> (3, 4).hlisted.map(double).tupled
res0: (Int, Int) = (6,8)

如果元组的元素具有不同的类型,您可以使用具有特定类型情况的多态函数进行映射,

scala> object frob extends Poly1 
     |   implicit def caseInt     = at[Int](_*2)
     |   implicit def caseString  = at[String]("!"+_+"!")
     |   implicit def caseBoolean = at[Boolean](!_)
     | 
defined module frob

scala> (23, "foo", false, "bar", 13).hlisted.map(frob).tupled
res1: (Int, String, Boolean, String, Int) = (46,!foo!,true,!bar!,26)

更新

从 shapeless 2.0.0-M1 开始,直接支持对元组的映射。上面的例子现在看起来像这样,

scala> import shapeless._, poly._, syntax.std.tuple._
import shapeless._
import poly._
import syntax.std.tuple._

scala> object double extends (Int -> Int) (_*2)
defined module double

scala> (3, 4) map double
res0: (Int, Int) = (6,8)

scala> object frob extends Poly1 
     |   implicit def caseInt     = at[Int](_*2)
     |   implicit def caseString  = at[String]("!"+_+"!")
     |   implicit def caseBoolean = at[Boolean](!_)
     | 
defined module frob

scala> (23, "foo", false, "bar", 13) map frob
res1: (Int, String, Boolean, String, Int) = (46,!foo!,true,!bar!,26)

【讨论】:

这太棒了!我会将您的答案标记为已接受,但我不知道是否可以在几年后更改已接受的答案。 你应该接受你认为最好的答案。如果情况或您的意见随着时间的推移发生变化,那么我认为相应地更新您的接受是合理的。 恕我直言,这东西太抽象了。我的意思是,它需要导入 3 个完整的命名空间(导入 xxxxx._),对于完整的通用情况,它需要与元组中的项类型一样多的附加隐式。这当然取决于用例,但很可能一个简单、明确的代码在不损失效率的情况下更具可读性。 object double extends (Int -&gt; Int) (_*2) 导致我的 IDE 崩溃。 WTF 这条线在做什么?【参考方案3】:

map 函数获取A =&gt; B 并返回F[B]

def map[A, B](f: A => B) : F[B]

正如retronym 所写,Tuple2[A, B] 是一个 Bifunctor,因此您可以在 scalaz 或 cat 中查找 bimap 函数。 bimap 是一个映射元组两边的函数:

def bimap[A, B, C, D](fa: A => C, fb: B => D): Tuple2[C, D]

因为 Tuple[A, B] 包含 2 个值,并且只能映射一个值(按照约定为右值),所以您可以只为左侧返回相同的值并使用右侧 函数映射元组的正确值。

(3, 4).bimap(identity, _ * 2)

【讨论】:

以上是关于在 Scala 元组上使用功能组合器?的主要内容,如果未能解决你的问题,请参考以下文章

Scala 组合器解析器 - 区分数字字符串和变量字符串

Scala基础学习02

Scala组合功能问题

Scala的映射和元组操作

为啥我不能在 Swift 的双精度元组上使用 min()?

如何合并两个元组列表?