【问题标题】:Translating this monadic counter from Haskell to Scala将此一元计数器从 Haskell 转换为 Scala
【发布时间】:2016-09-03 10:39:46
【问题描述】:

我已经定义了一个monadic counter in Haskell,我正在尝试将其转换为 Scala,但到目前为止失败得很惨。简而言之,问题是将计数器实现为状态 monad,它从环境中读取计数器增量常量,并记录计数器的历史记录(其值序列)。

我的一个朋友改进了我的解决方案,并提出了这个简单的解决方案:

newtype Counter = Counter Int deriving (Eq)

instance Show Counter where
  show (Counter i) = show i

incWith :: MonadState Counter m => Int -> m ()
incWith n = let incCounter n' (Counter i) = Counter $ i + n'
            in modify (incCounter n)

inc :: (MonadReader Int m, MonadState Counter m, MonadWriter [Counter] m) => m ()
inc = ask >>= incWith >> get >>= tell . (:[])

compute :: (MonadReader Int m, MonadState Counter m, MonadWriter [Counter] m) => m ()
compute =
  local (const 3) $ do
    inc
    inc
    inc
    local (const 5) $ do
      inc
      inc

我已经尝试without success 将其编码为 Scala + (Cats | ScalaZ)。 Cats 的最新稳定版本缺少 WriterTlift 方法。在Scalaz 中使用ReaderWriterState,我在几个小时内无法弄清楚如何使用local 方法。而这仅仅是开始......

这个 Haskell 解决方案如何以简单而优雅的方式翻译? (在语言允许的范围内)。

旁注:

我仍在试图弄清楚为什么我需要花这么多时间将简单的解决方案从 Haskell 转换为 Scala + FP 库(Cats、Scalaz)。在 Haskell 中,查找每个类型类的实例和可用函数是一件轻而易举的事,在使用 IntelliJ、GitHub 和 StackOverflow 的 Scala 中,这需要我几天的时间。所以我想知道我做错了什么,我该如何改善这种情况。

【问题讨论】:

  • 关于您的旁注:您认为 scala 文档对探索 API 没有帮助?
  • 目前还没有。我看到一个定义为类私有的方法的问题(例如上面的local),这意味着您不能直接使用它,而是通过这个私有类的多个实例之一。我认为您不能仅从 Scala 文档中做到这一点。
  • 关于您的旁注,Scala 标准库没有配备诸如 Monad 之类的代数。如果您想访问这些设施,那么您必须自己手动编写代码,或者像使用 Scalaz 或 Cats 一样。然而,由于语言的冗长和它的类型系统,在 Scala 中在 Haskell 中完成相同的工作相对而言需要更多的工作。正如 Edward Kmett 所说,Haskell 为他提供了更高的功率重量比。你的挫败感证明了这一点。
  • @Samar,我想说最大的障碍是他们无法将任何 OOP 知识转移到 FP 范式中。如果我们甚至采用 FP 中最基本的引用透明性的基本概念,命令式程序员就会立即被难住。首先,他们必须明白 FP 中函数的概念是数学函数,而不是命令式意义上的函数/过程(有时包含隐含的环境)。
  • 如果我们去掉了 OOP 程序员的基本工具,比如继承,那么你几乎已经瘫痪了他。而且我们甚至还没有开始讨论 FP 中的其他 FP 基本概念,例如 HOF、lambda 演算、ADT、代数和余代数等......

标签: scala haskell functional-programming scalaz scala-cats


【解决方案1】:

如果我正确理解您的意图,这将转化为非常简单易懂的代码:

  import scalaz._
  import Scalaz._
  val SM = ReaderWriterState.rwstMonad[Id, Int, List[String], Counter]

  case class Counter(value: Int)

  def incWith(n: Int): State[Counter, Unit] = for {
    v ← get[Counter]
    _ ← put(Counter(v.value + n))
  } yield ()

  def inc: IRWS[Int, List[String], Counter, Counter, Unit] = for {
    v ← SM.ask
    c ← SM.get
    _ ← SM.tell(List(s"Incrementing $c by $v "))
    _ ← SM.modify(counter ⇒ Counter(counter.value + v))
  } yield ()

  def compute: IRWS[Int, List[String], Counter, Counter, Unit] = {
    for {
      _ <- SM.local[Unit](i ⇒ 3)(for {
        _ ← inc
        _ ← inc
        _ ← inc
      } yield ())
      _ <- SM.local[Unit](i ⇒ 5)(for {
        _ ← inc
        _ ← inc
        _ ← inc
      } yield ())
    } yield ()
  }

  println(incWith(5)(Counter(0)))
  println(inc.run(4, Counter(0)))
  println(compute.run(0, Counter(0)))

【讨论】:

  • 谢谢(再次;))。我稍作修改,并将生成的代码推送到我上面链接的存储库。不过我有几个问题。 Scalaz 的新手怎么会发现您需要使用ReaderWriterState.rwstMonad。另外,您是如何确定需要使用 IRWS 的?我真的很好奇这个思考过程,以及它需要多少 Scalaz 经验。如果我必须继续为食物编写 Scala,我正在考虑制作自己的 FP 库......
  • 顺便说一句,如果我尝试使用Seq 进行此操作,则找不到SeqMonoid 实例。这是在 Scalaz 中的任何地方定义的吗?
  • 最后,我真的很高兴看到这里发布的两个解决方案(Haskell 与 Scala),这清楚地告诉我,我尽我所能达到一个点并没有错我可以在 Haskell 中以编程为生。我希望这对其他人也有帮助。
  • 欢迎您!从最短的角度看:我认为SeqMonoid 没有直接在任何地方定义,因为Seq 不够具体。不过,您可以为VectorIndSeq 以及任何其他PlusEmpty (SemigroupK) 实例提供幺半群:val x1 = Seq(1) implicit val m = scalaz.IndSeq.indSeqInstance.monoid[Int] IndSeq.fromSeq(x1) |+| IndSeq.fromSeq(x1)
  • 关于 IRWS - 你可以看到你需要三个 monad - ReaderWriterState。我的一般方法是通过转换器,但它们已经在特定特征MonadReaderMonadWriter 等中实现。然后只需遵循类型层次结构,您就会发现IRWST 实现了所有这些。通过在对象 ReaderWriterStateT 上按 intellij 中的点(向您显示可用的方法),我看到 #rwst 返回您 IRWST 实例。
猜你喜欢
  • 2017-04-17
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
  • 1970-01-01
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多