我正在尝试将Writer和State(通过Lens)结合起来。我很确定我需要单子转换器,但我很难弄清楚如何使用T版本以及如何正确构建它。
现在我有一些模型(简化):
case class Schedule(due: LocalDate)
case class Task(title: String, schedule: Schedule)
为titleL
, scheduleL
和dueL
每个领域定义的镜头。
我的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
}
我该如何处理?我对单极变压器的理解对吗?还有什么我可能错过的已经处理我案子的东西吗?
编辑:
我得到了一些像这样工作的东西,对于那个用例来说很好,但是对于更复杂的用例,我想要一些更好地与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
}
也许你可以进一步简化它,但这是我能得到的最好的。据我所知,你不能直接在单极变压器中使用镜头,但你可以将镜头转换为状态,这接近你所需要的。
首先让我们定义单子。
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 ()
话虽如此,我不太确定,它是不是比你提出的解决方案更好。