【问题标题】:Idiomatic Scala translation of Kiselyov's zippers?Kiselyov 拉链的惯用 Scala 翻译?
【发布时间】:2013-04-03 03:47:00
【问题描述】:

Oleg Kiselyov showed how to make a zipper from any traversable 使用定界延续。他的 Haskell 代码很短:

module ZipperTraversable where 

import qualified Data.Traversable as T
import qualified Data.Map as M


-- In the variant Z a k, a is the current, focused value
-- evaluate (k Nothing) to move forward
-- evaluate (k v)       to replace the current value with v and move forward.

data Zipper t a = ZDone (t a) 
                | Z a (Maybe a -> Zipper t a)

make_zipper :: T.Traversable t => t a -> Zipper t a
make_zipper t = reset $ T.mapM f t >>= return . ZDone
 where
 f a = shift (\k -> return $ Z a (k . maybe a id))

-- The Cont monad for delimited continuations, implemented here to avoid
-- importing conflicting monad transformer libraries

newtype Cont r a = Cont{runCont :: (a -> r) -> r}

instance Monad (Cont r) where
    return x = Cont $ \k -> k x
    m >>= f  = Cont $ \k -> runCont m (\v -> runCont (f v) k)

-- Two delimited control operators,
-- without answer-type polymorphism or modification
-- These features are not needed for the application at hand

reset :: Cont r r -> r
reset m = runCont m id

shift :: ((a -> r) -> Cont r r) -> Cont r a
shift e = Cont (\k -> reset (e k))

我在尝试在 Scala 中实现它时遇到了一些问题。我开始尝试使用 Scala 的 delimited continuations 包,但即使使用 Rompf 的 richIterable 想法推广到 @cps[X] 而不是 @suspendable,也不可能让提供的函数返回与提供的函数不同的类型用于重置.

我尝试按照 Kiselyov 的定义实现 continuation monad,但 Scala 很难对类型参数进行柯里化,而且我无法弄清楚如何以 scalaz 的 traverse 方法满意的方式将 Cont[R] 转换为 monad。

我是 Haskell 和 Scala 的初学者,非常感谢您的帮助。

【问题讨论】:

    标签: scala haskell monads continuations zipper


    【解决方案1】:

    您可以使用延续插件。在插件完成翻译工作后,它与来自 Oleg 的 Cont monad 和 shiftreset 有相似之处。棘手的部分是找出类型。所以这是我的翻译:

    import util.continuations._
    import collection.mutable.ListBuffer
    
    sealed trait Zipper[A] { def zipUp: Seq[A] }
    case class ZDone[A](val zipUp: Seq[A]) extends Zipper[A]
    case class Z[A](current: A, forward: Option[A] => Zipper[A]) extends Zipper[A] {
      def zipUp = forward(None).zipUp
    }
    
    object Zipper {
      def apply[A](seq: Seq[A]): Zipper[A] = reset[Zipper[A], Zipper[A]] {
        val coll = ListBuffer[A]()
        val iter = seq.iterator
        while (iter.hasNext) {
          val a = iter.next()
          coll += shift { (k: A=>Zipper[A]) =>
            Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
          }
        }
        ZDone(coll.toList)
      }
    }
    

    continuation 插件支持while 循环,但不支持mapflatMap,因此我选择使用while 和可变ListBuffer 来捕获可能更新的元素。 make_zipper 函数被翻译成伴随的 Zipper.apply - 一个用于创建新对象或集合的典型 Scala 位置。数据类型被翻译成一个密封的特征,两个案例类对其进行了扩展。我已经将 zip_up 函数作为Zipper 的一个方法,每个案例类都有不同的实现。也很典型。

    它在行动:

    object ZipperTest extends App {
      val sample = for (v <- 1 to 5) yield (v, (1 to v).reduceLeft(_ * _))
      println(sample) // Vector((1,1), (2,2), (3,6), (4,24), (5,120))
    
      def extract134[A](seq: Seq[A]) = {
        val Z(a1, k1) = Zipper(seq)
        val Z(a2, k2) = k1(None)
        val Z(a3, k3) = k2(None)
        val Z(a4, k4) = k3(None)
        List(a1, a3, a4)
      }
      println(extract134(sample)) // List((1,1), (3,6), (4,24))
    
      val updated34 = {
        val Z(a1, k1) = Zipper(sample)
        val Z(a2, k2) = k1(None)
        val Z(a3, k3) = k2(None)
        val Z(a4, k4) = k3(Some(42 -> 42))
        val z = k4(Some(88 -> 22))
        z.zipUp
      }
      println(updated34) // List((1,1), (2,2), (42,42), (88,22), (5,120))
    }
    

    我如何确定shiftkreset 的类型,或者如何翻译T.mapM

    我查看了mapM,我知道它可以让我获得Cont,但我不确定Cont 里面是什么,因为它取决于班次。所以我开始轮班。忽略haskell return 来构造Cont,shift 返回Zipper。我还猜想我需要在我的集合中添加一个A 类型的元素来构建。因此,移位将在“洞”中,其中需要A 类型的元素,因此k 将是A=&gt;? 函数。让我们假设这一点。我在我不太确定的类型后面加上问号。所以我开始了:

    shift { (k: A?=>?) =>
      Z(a, ?)
    }
    

    接下来我(认真地)查看(k . maybe a id)。函数maybe a id 将返回一个A,因此这与k 作为参数所接受的一致。它相当于a1.getOrElse(a)。另外因为我需要填写Z(a, ?),所以我需要弄清楚如何从选项中获取一个函数到Zipper。最简单的方法是假设k 返回一个Zipper。另外,看看如何使用拉链k1(None)k1(Some(a)),我知道我必须为用户提供一种方法来选择性地替换元素,这就是forward 函数的作用。它继续原来的a 或更新的a。它开始变得有意义。所以现在我有:

    shift { (k: A=>Zipper[A]) =>
      Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
    }
    

    接下来我再看mapM。我看到它是由return . ZDone 组成的。再次忽略return(因为它仅适用于Cont monad),我看到ZDone 将获取结果集合。所以这是完美的,我只需将coll 放入其中,当程序到达那里时,它将具有更新的元素。此外,reset 中的表达式类型现在与k 的返回类型一致,即Zipper[A]

    最后我填写了编译器可以推断的重置类型,但是当我猜对时,它给了我一种(错误的)自信感,我知道发生了什么。

    我的翻译不像 Haskell 那样通用或漂亮,因为它不保留集合中的类型并使用突变,但希望它更容易理解。

    编辑:这是保留类型并使用不可变列表的版本,以便z.zipUp == z.zipUp

    import util.continuations._
    import collection.generic.CanBuild
    import collection.SeqLike
    
    sealed trait Zipper[A, Repr] { def zipUp: Repr }
    case class ZDone[A, Repr](val zipUp: Repr) extends Zipper[A, Repr]
    case class Z[A, Repr](current: A,
        forward: Option[A] => Zipper[A, Repr]) extends Zipper[A, Repr] {
      def zipUp = forward(None).zipUp
    }
    
    object Zipper {
      def apply[A, Repr](seq: SeqLike[A, Repr])
                        (implicit cb: CanBuild[A, Repr]): Zipper[A, Repr] = {
        type ZAR = Zipper[A, Repr]
    
        def traverse[B](s: Seq[A])(f: A => B@cps[ZAR]): List[B]@cps[ZAR] =
          if (s.isEmpty) List()
          else f(s.head) :: traverse(s.tail)(f)
    
        reset {
          val list = traverse(seq.toSeq)(a => shift { (k: A=>ZAR) =>
            Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
          })
          val builder = cb() ++= list
          ZDone(builder.result): ZAR
        }
      }
    }
    

    顺便说一下,这里有关于 scala 中 continuation monad 的其他资源:

    【讨论】:

    • 感谢您提供如此详细的答案! (顺便说一句:如果你想编辑它,我认为你在 extract134 的第二行中的意思是“seq”而不是“sample”。)“它不保留集合中的类型”是什么意思?我没有在代码中看到“任何”。
    • 另外,您说延续不支持地图,但这就是我在指向 Rompf 幻灯片的链接时所指的内容(有关支持延续的地图的定义,请参见幻灯片 48)。我需要以纯粹的函数式风格来做这件事,所以我将从您编写的内容开始并尝试对其进行修改。有什么建议吗?
    • 我想出了如何使用 Rompf 的可悬挂地图制作一个纯功能版本:pastie.org/7460281 感谢您的帮助!
    • 谢谢,已将样本更正为 seq。关于类型,当初始输入为Vector 时,我的版本返回List,例如Zipper(Vector(1)).zipUp。您的带有CanBuildFrom 的版本解决了这个问题。请注意,带有 Rompf 的可挂起的版本仍在使用可变的Builder。因此,您将使用以下代码遇到错误行为:{val z = Zipper(Vector(1)); z.zipUp == z.zipUp}。它将返回false
    • 哦,您对 Rompf 的版本是正确的——我没有发现 :( 我太关心解决编译器错误。感谢您的测试用例!
    猜你喜欢
    • 2012-06-07
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多