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

4
我将尝试结合Writer和State(通过Lens)。我很确定需要使用单子变换器,但我很难弄清楚如何使用T版本以及如何正确构建此应用。
目前我有一些模型(简化版):
case class Schedule(due: LocalDate)
case class Task(title: String, schedule: Schedule)

为每个字段定义了镜头:titleLscheduleLdueL

我的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
}

我该如何处理这个问题?使用单子变换器是否正确?还有其他已经处理过我这种情况的方法吗?


编辑:

我已经实现了一些类似这样的东西,对于那个用例来说很好,但是我想要更好地与状态集成以处理更复杂的情况:

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
}
1个回答

0
也许你可以进一步简化它,但这是我能得到的最好结果。 据我所知,你不能直接在monad transformer中使用lens,但你可以将lens转换为state,这接近于你所需的。
首先,让我们定义我们的monads。
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函数,以便能够在单子内部询问它。

就像你的例子一样 - 你想要有一个特定的条件来写入日志,所以你需要获取初始状态来知道标题是否已更改。这变得不太简洁:

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 ()

话虽如此,我并不确定你提出的解决方案是否更好。


正如你所说,它看起来与我最终得出的结果相似,但随着我学习更多,我开始发现我可能没有正确地对整个事物进行建模。我有一种感觉RWS会出现,所以我会将其作为一个例子保留下来,谢谢你。 - willtrnr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接