3 ms·
You can do something like this: class MyTransaction { fun begin() {} fun commit() {} } fun MyTransaction.query() {} fun MyTran
by kllrnohj 8y ago
You can do something like this:
class MyTransaction {
fun begin() {}
fun commit() {}
}
fun MyTransaction.query() {}
fun MyTransaction.update() {}
inline fun <R> transaction(tblock: MyTransaction.() -> R): R {
val t = MyTransaction()
t.begin()
try {
return t.tblock()
} finally {
t.commit()
}
}
fun test() {
// update() doesn't compile, no such method
val hello = transaction {
query()
update()
query()
"Hello"
}
println("$hello") // prints "Hello"
}
Does that not work for your usage for some reason? Is there a problem with this approach I'm not seeing?
- lmm 8y agoI must admit I don't entirely understand how your approach works, but it seems like it requires all the functions you want to use in transactions to live on MyTransaction (even if only as extension methods)? I want to be able to write normal functions including quite high-level ones that take some business objects and return MustHappenInTransaction[SomeOtherBusinessObject], and pass these return values through generic methods that don't know anything about transactions specifically.
- kllrnohj 8y agotypealias MustBeInTransaction = MyTransaction.() -> Unit fun makeObject(i: Int): MustBeInTransaction { return { /* do something with i or whatever in a transaction */ } } fun test() { var genericArray = arrayOf(1, 2, 3).map { makeObject(it) } //genericArray.forEach { it() } // fails to compile transaction { genericArray.forEach { it() } } } Like that?
- eeperson 8y agoIt just dawned on me that Kotlin receivers[1] and extension methods[2] are basically the equivalent to Scala implicit params and conversions respectively. Although it appears that receivers can only really be used with anonymous functions. This appears to be similar to implicit function types proposed for Dotty[3]. So the Dotty equivalent to the above would be: type MustBeInTransaction = implicit MyTransaction => Unit def makeObject(i: Int): MustBeInTransaction = { return { /* do something with i or whatever in a transaction */ } } def test(): Unit = { var genericArray = Array(1, 2, 3).map(makeObject) //genericArray.forEach { _() } // fails to compile transaction { genericArray.forEach { _() } } } EDIT - I just noticed that this is almost exactly the same as the example used in the function types link below [1] https://stackoverflow.com/questions/45875491/what-is-a-receiver-in-kotlin https://stackoverflow.com/questions/45875491/what-is-a-recei... [2] https://kotlinlang.org/docs/reference/extensions.html https://kotlinlang.org/docs/reference/extensions.html [3] https://www.scala-lang.org/blog/2016/12/07/implicit-function-types.html https://www.scala-lang.org/blog/2016/12/07/implicit-function...
- eeperson 8y agoIt is interesting to see that is possible in Kotlin. I just learned a lot about receivers (as an aside, do you know where I can find better documentation? The best I could find was a stack overflow post). I think this issue with this is what happens when you combine DB transactions with async queries with errors and null handling? It seems like you have to combine 4 different mechanisms in order to do this (receivers, async/await, try/catch, if(null)). Is there some way to do this all with receivers? It seems like they don't really compose. The 'command' version in Scala could be written roughly the same way for any combination of the 4 features (the types would just change). For example you could write your original example as: def test() { // update() compiles but doesn't do anything to the DB val helloDBOperation = for { result1 <- query() _ <- update() result2 <- query2() } yield "Hello" println("$hello") // prints "Hello" }
- eeperson 8y agoIt is interesting to see that is possible in Kotlin. I just learned a lot about receivers (as an aside, do you know where I can find better documentation? The best I could find was a stack overflow post). I think this issue with this is what happens when you combine DB transactions with async queries with errors and null handling? It seems like you have to combine 4 different mechanisms in order to do this (receivers, async/await, try/catch, if(null)). Is there some way to do this all with receivers? It seems like they don't really compose. The 'command' version in Scala could be written roughly the same way for any combination of the 4 features (the types would just change). For example you could write a Scala version of your original example as: def test(): Unit = { // update() compiles but doesn't do anything to the DB val helloDBOperation = for { _ <- query() _ <- update() _ <- query2() } yield "Hello" val hello = db.run(helloDBOperation) println("$hello") // prints "Hello" } This would look almost exactly the same no matter which combination of the 4 features you use. EDIT - it looks like I somehow also accidentally replied to you with an earlier version of this comment. Unfortunately , I didn't notice until it was too old to delete or edit. Please ignore the other comment that is very similar to this one. Sorry about the confusion.
- kllrnohj 8y ago> as an aside, do you know where I can find better documentation? The best I could find was a stack overflow post https://kotlinlang.org/docs/reference/type-safe-builders.html https://kotlinlang.org/docs/reference/type-safe-builders.htm... It's more designed to build DSLs or similar, so you'll find more documentaion/examples in that area of the docs. > I think this issue with this is what happens when you combine DB transactions with async queries with errors and null handling? It seems like you have to combine 4 different mechanisms in order to do this (receivers, async/await, try/catch, if(null)). Is there some way to do this all with receivers? It seems like they don't really compose. It's just a closure with a compiler-added paremeter. It can be nullable if you want and exceptions work the same as any other closure. If you want async/await you'd use coroutines. I think coroutines and receivers mix just fine, you'd just make the type a suspending lambda with receiver, but I've never tried.
- lmm 8y agoYeah, like that. But if code like that is permitted, is there anything that stops me writing a method that stashes a MyTransaction.() in a variable and then using it in another method long after the transaction it's supposed to represent has been closed?