我第一次尝试实现一个读取器monad。
我想使用一元风格来查询数据库。
用例1:用户与同事有一对一的关系.伪码是getUserById(getUserById(id).getColleague())
用例2:按id检索用户列表。伪码是List(getUserById(id1), getUserById(id2))
这似乎是monads的一个很好的用例。我的目标是看看我是否可以利用monad来改进我的代码。
PS :请提供至少一个答案,没有黄曲柳。
以下是代码:
package monad
import com.mongodb.casbah.Imports._
object Monad {
type UserId = Int
case class User(id: UserId, name: String, colleagueId: UserId)
trait Reader[I, A] { self =>
def run(id: I) : A
def map[B](f: A => B) : Reader[I, B] =
new Reader[I, B] { def run(id: I) = f(self.run(id)) }
def flatMap[B](f: A => Reader[I, B]) : Reader[I, B] =
new Reader[I, B] { def run(id: I) = f(self.run(id)).run(id) }
}
def coll = MongoClient()("test")("user")
def DBObject2User(o: DBObject) : User = User(o.as[Double]("id").toInt, o.as[String]("name"), o.as[Double]("colleague").toInt)
// Strange design, id is not used…
def User2Colleague(u: User) : Reader[UserId, DBObject] =
unit(coll.findOne(MongoDBObject("id" -> u.colleagueId)).get)
def GetUserById : Reader[UserId, DBObject] =
Reader { id: UserId => coll.findOne(MongoDBObject("id" -> id)).get }
def GetUserById2 : Reader[UserId, User] = GetUserById.map(DBObject2User)
def unit[A](a: => A) = Reader { id: UserId => a }
object Reader {
def apply[I, A](f: I => A) = new Reader[I, A] { def run(i: I) = f(i) }
}
def main(args: Array[String]) {
// I can do
println(GetUserById2.run(1))
// Same with for comprehension
val userReader = for (io <- GetUserById2) yield io
println(userReader.run(1))
//Combination to explore one-to-one relation
val user = GetUserById2.run(1)
val colleague = GetUserById2.run(user.colleagueId)
// Same with flatMap
println(GetUserById2.flatMap(User2Colleague).run(1))
// Same with for-comprehension but doesn't work
val io = for {io <- GetUserById2
io2 <- User2Colleague(io).map(DBObject2User)} yield io2
println(io.run(1))
//TODO: List[Reader] to Reader[List]
}
}这是个好办法吗?我有一些疑问,参见我的评论Strange design
我如何改进我的代码?
发布于 2013-10-29 22:32:11
我试着重新修改你的建议,使收集成为读者的输入,重新修改命名,因为我走了。
package monad
package object reader{
type UserId = Int
}
package reader {
case class User(id: UserId, name: String, colleagueId: UserId)
import com.mongodb.casbah.Imports._
import com.mongodb.casbah
trait Reader[I, A] {
self =>
val run = apply _
def apply(id:I):A
def map[B](f: A => B): Reader[I, B] =
new Reader[I, B] {
def apply(id: I) = f(self.run(id))
}
def flatMap[B](f: A => Reader[I, B]): Reader[I, B] =
new Reader[I, B] {
def apply(id: I) = f(self(id)).run(id)
}
}
object Reader {
def unit[A](a: => A) = apply {
id: UserId => a
}
def apply[I, A](f: I => A) = new Reader[I, A] {
def apply(i: I) = f(i)
}
}
object Users {
def asUser(o: DBObject): User = User(o.as[Double]("id").toInt, o.as[String]("name"), o.as[Double]("colleague").toInt)
def colleague(u: User): Reader[MongoCollection, User] =
Reader{
coll => asUser(coll.findOne(MongoDBObject("id" -> u.colleagueId)).get)
}
def getUserById(id:UserId): Reader[MongoCollection, User] =
Reader {
coll => asUser(coll.findOne(MongoDBObject("id" -> id)).get)
}
}
object Client extends App {
import Users._
def coll: casbah.MongoCollection = MongoClient()("test")("user")
// I can do
println(getUserById(1)(coll))
// Same with for comprehension
val userReader = for (user <- getUserById(1)) yield user
println(userReader(coll))
//Combination to explore one-to-one relation
val user = getUserById(1)(coll)
val otherUser = getUserById(user.colleagueId)(coll)
// Same with flatMap
println(getUserById(1).flatMap(colleague)(coll))
// Same with for-comprehension but doesn't work
val coworkerReader = for {user <- getUserById(1)
coworker <- colleague(user)} yield coworker
println(coworkerReader(coll))
}
}使用这种方法,我认为代码更容易测试,因为您可以传递依赖项( MongoCollection),同时只操作签名中的值和函数。在http://blog.originate.com/blog/2013/10/21/reader-monad-for-dependency-injection/上阅读更多(我不是作者,但这是一个清晰的解释)
发布于 2013-11-07 20:22:06
读者单台和单台变压器一步一步+与mongoDB的例子,很好地描述这里.
发布于 2013-10-29 13:58:53
您的用法并没有那么糟糕,我感到困惑的是,通常认为读取器的配置输入(比如db连接数据)是被检查用户的id。
首先,为了在这样的操作中使用一个著名的名称,我会将IO[I,A]名称更改为Reader[I,A] *
至于User2Colleague(u: User) : IO[UserId, DBObject]方法,放弃读取器输入以提供封装在monad中的常量值并不少见:这正是您的unit方法所做的!
事实上,我会把它改成
def User2Colleague(u: User) : Reader[UserId, DBObject] =
unit(coll.findOne(MongoDBObject("id" -> u.colleagueId)).get)或者更符合客户端代码的用法。
def User2Colleague(u: User): DBObject = coll.findOne(MongoDBObject("id" -> u.colleagueId)).get
...
def main(args: Array[String]) {
val mng = new Mongo()
val io = for {
io <- mng
io2 <- unit(DBObject2User(io))
io3 <- unit(User2Colleague(io2))
} yield (DBObject2User(io3))
println(io.run(1)) 我建议的是,除非需要,否则编写代码时要尽可能纯粹(即没有一元效应)。这意味着在常规函数中执行映射,然后只在需要时使用unit进行包装(例如在“理解”中进行组合打印)。
*通常的IO monad没有输入类型,它只是一个输出类型,通常是针对用户I/O (控制台、打印、电子邮件、发射火箭等),即任何对程序本身有副作用的东西。
https://stackoverflow.com/questions/19637505
复制相似问题