Scalaz:结合 Writer 和 State(和/或 Lens)

标签 scala monads scalaz

我正在尝试将 Writer 和 State(通过 Lens)结合起来。我很确定我需要 monad 转换器,但是我很难弄清楚如何使用 T 版本以及如何正确构建它。

现在我有一些模型(简化):

case class Schedule(due: LocalDate)
case class Task(title: String, schedule: Schedule)

为每个字段定义的镜头,titleL , scheduleLdueL .

我的 Writer 的类型别名 type Logger[A] = Writer[Vector[String], A]
还有一些功能来修改我的模型:
def changeTitle(title: String): Task => Logger[Task] = { t: Task =>
  for {
    a <- titleL.set(t, title).point[Logger]
    _ <- ("Title changed to " + a.title).point[Vector].tell whenM (a.title != t.title)
  } yield a
}

def changeDue(date: LocalDate): Schedule => Logger[Schedule] = { s: Schedule =>
  for {
    a <- dueL.set(s, date).point[Logger]
    _ <- ("Due changed to " + a.due).point[Vector].tell whenM (a.due != s.due)
  } yield a
}

但是现在我不确定如何将镜头或状态方法与最后一个功能一起使用。

我希望能够做一些看起来像这样的事情:
def reschedule(date: LocalDate): Task => Logger[Task] = { t: Task =>
  (for {
    a <- scheduleL %= reschedule(date)
    _ <- ("Reschedule: " + a.schedule).point[Vector].tell whenM (a.schedule != t.schedule)
  } yield a) exec t
}

我应该如何处理这个问题?我在使用 monad 变压器的正确轨道上吗?还有什么我可能错过的已经处理我的案子的吗?

编辑:

我得到了这样的工作,对于那个用例来说很好,但我想要一些与 State 更好地集成的东西来处理更复杂的情况:
def reschedule(date: LocalDate): Task => Logger[Task] = { t: Task =>
  for {
    sa <- scheduleL.get(t).point[Logger]
    sb <- changeDue(date)(sa)
    a <- scheduleL.set(t, sb).point[Logger]
    _ <- ("Reschedule: " + a.schedule).point[Vector].tell whenM (a.schedule != t.schedule)
  } yield a
}

最佳答案

也许你可以进一步简化它,但这是我能得到的最好的。
据我所知,您不能直接在 monad 转换器中使用镜头,但您可以将镜头转换为状态,这与您需要的很接近。

首先让我们定义我们的 monad。

def RWA[R, A] = ReaderWriterState.rwstMonad[Id.Id, R, Vector[String], A]
val RST = RWA[String, Task]
val RLS = RWA[Long, Schedule]


  def changeTitleV1 = for {
    title ← RST.ask // Reader part of transformer getting new title from the environment
    _ ← RST.modify(titleL =>= (_ ⇒ title))  // `=>=` is converting lens to `A => A`
    _ ← RST.tell(Vector(s"I put the value $title")))
  } yield ()


changeTitleV1.run("new title", Task("old title", Schedule(123)))  //(Vector(I put the value new title),(),Task(new title,Schedule(123)))

我们将我们的新标题作为第一个参数传递给这个 run函数能够在 monad 内部询问它。

就像你的例子一样 - 你想要在日志中写入某个条件,所以你需要获取初始状态以知道标题是否已更改。它变得不那么简洁:
def changeTitleV2 = for {
    title ← RST.ask
    task0 ← RST.get
    _ ← RST.put(titleL.set(task0, title))
    _ ← RST.whenM(task0.title != title)(RST.tell(Vector(s"I put the value $title")))
  } yield ()

当然,您可以为 changeDue 定义相同的内容:
  def changeDue = for {
    d0 ← RLS.get
    due ← RLS.ask
    _ ← RLS.put(dueL.set(t0, due))
    _ ← RLS.whenM(d0.due != due)(RLS.tell(Vector(s"due changed to $due")))    
  } yield ()

也就是说,我不太确定,你想出的解决方案要好得多。

关于Scalaz:结合 Writer 和 State(和/或 Lens),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40598378/

相关文章:

haskell - 如何在 ADT 或记录语法上使用 Monadic 绑定(bind)运算符

scala - 每个可遍历的单子(monad)是否都有一个伴随序列

scala - 如何获得可用于符号方法的scalaz IDEA实时模板?

scala - 什么时候使用来自 scalaz 的 monad?

c# - 为什么我们应该避免公共(public)方法?封装的好处

scala - 如何使用带有通用参数的结构类型?

java - 使用 Scala 中的 vararg 参数覆盖 Java 方法时出现 AbstractMethodError

scala - 类型与 Spark-shell 中的相同类型不匹配

scala - 为什么 vector[Option[Int]] 上的 flatMap 其映射器函数结果不是 Vector[Option[Int]] 有效?

haskell - 使用 `MonadBaseControl` API