笛卡尔积在斯卡拉兹中

时间:2012-02-06 15:13:55

标签: scala functional-programming traversal scalaz

在论文Eric Torreborre's blogpostEssence of the Iterator Pattern中,他描述了导线的笛卡尔积也是如何遍历。

任何人都可以使用scalaz library向我展示一个例子,因为我无法弄明白。让我们说问题是,对于List[Int],我想提供两个:

  1. 列表中元素的Int总和
  2. 通过将“Z”附加到List[String] s
  3. 的字符串表示形式来创建元素Int

    我的理解是我可以使用traverse执行此操作,但这样做的方式只是实际遍历我的结构一次,与此解决方案不同:

    val xs = List(1, 2, 3, 4)
    val (sum, strings)  = (xs.sum, xs map (_.toString + "Z"))
    

    注1 - 我知道还有其他方法可以做到这一点,我不需要遍历这个例子,也不一定是最简单的解决方法。但是,我试图了解遍历,所以我真的在寻找所述问题的答案


    编辑 - 感谢下面的 missingfaktor ,了解如何使用State执行此操作。我想我想知道的是我如何组成两个独立的计算。例如;我的功能概念如下:

    val shape = (_ : List[Int]) map (_.toString + "Z")
    val accum = (_ : List[Int]).sum
    

    我想让这些累积独立的机制相互依赖,然后选择是否使用 两者遍历我的List[Int]

    xs traverse shape //A List[String]
    xs traverse accum //An Int
    
    xs traverse (shape <x> accum) //The pair (List[String], Int)
    

    Eric意味着这是可能的,但我不知道如何做到这一点〜即我没有看到如何定义shapeaccum以便它们可以被组合,以及如何撰写它们。

    注意2 shapeaccum并不意味着具有上述签名的功能。它们是具有执行上述遍历所必需的类型的表达式。

4 个答案:

答案 0 :(得分:4)

我在Jason的建议中添加了我自己的答案,以显示遍历列表的不同方式:

import org.specs2._
import scalaz.std.anyVal._, scalaz.std.list._
import scalaz._, std.tuple._
import scalaz.{Monoid, Applicative}

class TraverseSpec extends mutable.Specification {

  implicit val Sum = Monoid[Int].applicative
  implicit val Concat = Monoid[List[String]].applicative
  implicit val A: Applicative[({type λ[α] = (Int, List[String])})#λ] = Sum.product[({type λ[α]=List[String]})#λ](Concat)
  val xs = List(1, 2, 3, 4)

  "traverse - by folding the list with a Monoid" >> {
    val (sum, text) = Foldable[List].foldMap(xs)(a => (a, List(a.toString + "Z")))
    (sum, text) === (10, List("1Z", "2Z","3Z", "4Z"))
  }
  "traverse - with a function returning a tuple" >> {
    val (sum, text) = A.traverse(xs)(a => (a, List(a.toString + "Z")))
    (sum, text.reverse) === (10, List("1Z", "2Z","3Z", "4Z"))
  }
  "traverse - with 2 functions and 2 traversals" >> {
    val count   = (a: Int) => a
    val collect = (a: Int) => List(a.toString+"Z")

    val sum  = Sum.traverse(xs)(count)
    val text = Concat.traverse(xs)(collect)

    (sum, text.reverse) === (10, List("1Z", "2Z","3Z", "4Z"))
  }
  "traverse - with 2 functions and 1 fused traversal" >> {
    val sum     = (a: Int) => a
    val collect = (a: Int) => List(a.toString+"Z")

    implicit def product[A, B, C](f: A => B): Product[A, B] = Product(f)
    case class Product[A, B](f: A => B) {
      def <#>[C](g: A => C) = (a: A) => (f(a), g(a))
    }

    val (total, text)  = A.traverse(xs)(sum <#> collect)
    (total, text.reverse) === (10, List("1Z", "2Z","3Z", "4Z"))
  }
}

我认为最后一个例子展示了你所追求的:2个独立定义的函数,可以组成只进行一次遍历。

答案 1 :(得分:3)

Debasish Ghosh在这个主题上写了一篇不错的post。基于该帖子中的代码:

scala> List(1, 2, 3, 4)
res87: List[Int] = List(1, 2, 3, 4)

scala> .traverse[({ type L[X] = State[Int, X] })#L, String] { cur =>
     |   state { (acc: Int) => (acc + cur, cur.toString + "Z") }
     | }
res88: scalaz.State[Int,List[String]] = scalaz.States$$anon$1@199245

scala> .apply(0)
res89: (Int, List[String]) = (10,List(1Z, 2Z, 3Z, 4Z))

修改

您有两个函数List[A] => BList[A] => C,并且您需要一个函数List[A] => (B, C)。这就是&&&的用途。这不会融合循环。我无法想象如何为这种情况融合循环。

Fwiw,代码:

scala> val shape = (_ : List[Int]) map (_.toString + "Z")
       val accum = (_ : List[Int]).sum
shape: List[Int] => List[java.lang.String] = <function1>
accum: List[Int] => Int = <function1>

scala> val xs = List(1, 2, 3, 4)
xs: List[Int] = List(1, 2, 3, 4)

scala> (shape &&& accum) apply xs
res91: (List[java.lang.String], Int) = (List(1Z, 2Z, 3Z, 4Z),10)

编辑2:

如果您有A => BA => C函数,则可以使用A => (B, C)将它们合并到&&&。现在,如果B : MonoidC : Monoid,您可以使用foldMap获取List[A] => (B, C)。这将在一个循环中完成。

代码:

scala> val f: Int => Int = identity
f: Int => Int = <function1>

scala> val g: Int => List[String] = i => List(i.toString + "Z")
g: Int => List[String] = <function1>

scala> List(1, 2, 3, 4).foldMap(f &&& g)
res95: (Int, List[String]) = (10,List(1Z, 2Z, 3Z, 4Z))

最终修改(我发誓我不会再次编辑此内容了。)

由于这些概念起源于Haskell,我认为在 Haskell 标签下重新发布这个问题是个好主意,我做到了。 The answer there似乎与我在这个帖子中所说的一致。 Hôpe这有帮助。

答案 2 :(得分:3)

你不会在这里看到一个大赢家,因为你只是将普通的'Monoids推广到Applicative中,所以你将它们融合在一起。

import scalaz.std.anyVal._, scalaz.std.list._, scalaz.std.string._
val Sum = Monoid[Int].applicative
val Concat = Monoid[List[String]].applicative
val A: Applicative[({type λ[α] = (Int, List[String])})#λ] = Sum.product[({type λ[α]=List[String]})#λ](Concat)

val xs = List(1, 2, 3, 4)
val (sum, text) = A.traverse(xs)(a => (a, List(a.toString + "Z")))
println(sum, text) // 10, List("1Z", "2Z", "3Z", "4Z")

也可以使用Monoid[(Int, List[String])]来解决所述问题:

import scalaz._, std.tuple._
val (sum1, text1) = Foldable[List].foldMap(xs)(a => (a, List(a.toString + "Z")))
println(sum1, text1) // 10, List("1Z", "2Z", "3Z", "4Z")

如果您想要遍历的其中一个效果是非平凡的Applicative,例如State,那么事情会变得更有趣。

答案 3 :(得分:2)

如果我理解正确的话,你应该在scala-seven分支示例中描述:WordCount。它还涉及国家。我在移动设备上,否则我会提供链接。

这是链接:

HTH Andreas

编辑:

好一些解释。我认为你的问题的根本问题是如何撰写功能或相应的应用。这可以通过应用上的产品方法来实现。

https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/Applicative.scala#L46

所以你需要为你的两个函数shape和accum定义applicative。如果累积将被建模为国家申请。

如果我们从这个例子看这行: val WordCount = StateT.stateMonad [Int] .compose({typeλ[α] = Int})#λ

它创造了一个“有效”的应用程序(对不起我的不良措辞)。通常在遍历中,您只有当前元素。但是如果你想在以前的计算中计算你需要状态,那么这会创建一个state-applicative,它为每个遍历的元素返回1(参见Monoid [Int] .applicative)。

现在实际上我们需要查看atWordStart方法,你需要定义一个可以使用构造的WordCount应用程序(使用State)的方法

这是scalaz 6的另一个例子,它更简单。我认为观察initialValue以及transform1方法的作用非常重要:

import scalaz._
import Scalaz._

object StateTraverseExample {

  type StS[x] = State[(Set[Int], Boolean), x] 

  def main(args: Array[String]): Unit = {
    println("apparently it works " + countAndMap(Vector.range(0, 20)))
  }

  def transform1(i: Int, l: Set[Int], result: Boolean): (Set[Int],Boolean) = {
    if (result || (l contains i))
      (l, true)
    else
      (l + i, false)
   }

  def countAndMap(l: Vector[Int]): (Set[Int],Boolean) = {
    val initialValue=(Set.empty[Int], false)

    val counts = l.traverse[StS, Unit] { i => 
      state { case (set, result) => (transform1(i,set,result), println(i))   }
    } ~> initialValue
    counts
  }
}

我现在记得因为这个话题对我很感兴趣。我问为什么埃里克在他的博文中没有提供适用的产品。他说他放弃了与类型签名的摔跤。那个时候杰森为scalaz7修了WordCount的例子(六个例子没有提供动作计数字)