I understand the usual "Task not serializable" issue that arises when accessing a field or a method that is out of scope of a closure.
To fix it, I usually define a local copy of these fields/methods, which avoids the need to serialize the whole class:
class MyClass(val myField: Any) {
def run() = {
val f = sc.textFile("hdfs://xxx.xxx.xxx.xxx/file.csv")
val myField = this.myField
println(f.map( _ + myField ).count)
}
}
Now, if I define a nested function in the run method, it cannot be serialized:
class MyClass() {
def run() = {
val f = sc.textFile("hdfs://xxx.xxx.xxx.xxx/file.csv")
def mapFn(line: String) = line.split(";")
val myField = this.myField
println(f.map( mapFn( _ ) ).count)
}
}
I don't understand since I thought "mapFn" would be in scope...
Even stranger, if I define mapFn to be a val instead of a def, then it works:
class MyClass() {
def run() = {
val f = sc.textFile("hdfs://xxx.xxx.xxx.xxx/file.csv")
val mapFn = (line: String) => line.split(";")
println(f.map( mapFn( _ ) ).count)
}
}
Is this related to the way Scala represents nested functions?
What's the recommended way to deal with this issue ?
Avoid nested functions?
Isn't it working in the way so that in the first case f.map(mapFN(_)) is equivalent to f.map(new Function() { override def apply(...) = mapFN(...) }) and in the second one it is just f.map(mapFN)? When you declare a method with def it is probably just a method in some anonymous class with implicit $outer reference to the enclosing class. But map requires a Function so the compiler needs to wrap it. In the wrapper you just refer to some method of that anonymous class, but not to the instance itself. If you use val, you have a direct reference to the function which you pass to the map. I'm not sure about this, just thinking out loud...
Related
I am using Groovy to create a package that I use in ReadyApi.
In a Groovy script test step, I do the following:
class B {
String value
boolean isSomething
}
class A {
String name
B propB
public A() {
this.name = "Maydan"
}
}
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true) }
And I get the following error:
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'B#6c218cea' with class 'B' to class 'A'
error at line: 15
Does someone know why? It doesn't make any sense to me.
Kind regards.
PS: I would like to create an instance of class A (by using its parameterless constructor) and setting its field propB in a single statement
You need to return your A object from the .with closure. You may do it like that:
def x = (A) new A().with { propB = new B(value: "Abc", isSomething: true); return it}
but to me personally it looks a little bit odd. I would do it like that:
def x = new A(propB: new B(value: "Abc", isSomething: true))
The same effect, but more compact and readable. It doesn't require to change your A and B definitions, this "map constructor" works out of the box in Groovy, it will call your parameterless constructor and then assigns the necessary fields (propB in your case).
I am trying to make the construction of instances of a class depending on the scope in which they are defined without using explicit parameters.
This is part of a port from Python to Kotlin but the main idea would be something like:
var d = MyClass()
use_scope(contextAForScope) {
var a = MyClass()
use_scope(contextBForScope) {
var b=MyClass()
}
}
In this example the d constructor would use a default context, a constructor would use contextAForScope and b constructor would use contextBForScope (use_scope is just a placeholder here).
Something like implicit contexts?
Of course, I could make the constructor parameter explicit but this will potentially be used many times in a single scope and I would prefer not to define an additional variable.
class MyClass(val context: Int)
fun MyClass() = MyClass(0)
interface MyClassScope {
fun MyClass(): MyClass
}
object ContextAForScope : MyClassScope {
override fun MyClass() = MyClass(1)
}
object ContextBForScope : MyClassScope {
override fun MyClass() = MyClass(2)
}
inline fun useScope(scope: MyClassScope, block: MyClassScope.() -> Unit) {
scope.block()
}
fun main(args: Array<String>) {
val d = MyClass()
useScope(ContextAForScope) {
val a = MyClass()
useScope(ContextBForScope) {
val b = MyClass()
}
}
}
Use a factory function to create your class. If you name the function like the class, it looks like a constructor.
Define an interface with the same factory function and two objects for the scopes.
Define a function that takes the scope and the initializer block.
Now you can use the useScope-Function and within the block the right factory function is invoked.
with is what you are looking for:
class MyClass()
var d = MyClass()
fun main(args: Array<String>){
var c = "c: Could be any class"
var d = "d: Could be any class"
with(c) {
// c is "this"
var a = MyClass()
print(c) // prints "c: Could be any class"
with(d) {
// d is "this"
var b = MyClass()
}
// b is undefined in this scope
}
// a is undefined in this scope
}
with takes a lambda as argument an everything in that lambda is only defined in that scope.
Consider a function that takes an interface implementation as an argument like this:
interface Callback {
fun done()
}
class SomeClass {
fun doSomeThing(callback: Callback) {
// do something
callback.done()
}
}
When I want to test the caller of this function, I can do something like
val captor = ArgumentCaptor.forClass(Callback::class)
Mockito.verify(someClass).doSomeThing(captor.capture())
To test what the other class does when the callback is invoked, I can then do
captor.value.done()
Question: How can I do the same if I replace the callback interface with a high order function like
fun doSomeThing(done: () -> Unit) {
// do something
done.invoke()
}
Can this be done with ArgumentCaptor and what class do I have to use in ArgumentCaptor.forClass(???)
I recommend nhaarman/mockito-kotlin: Using Mockito with Kotlin
It solves this through an inline function with a reified type parameter:
inline fun <reified T : Any> argumentCaptor() = ArgumentCaptor.forClass(T::class.java)
Source: mockito-kotlin/ArgumentCaptor.kt at a6f860461233ba92c7730dd42b0faf9ba2ce9281 ยท nhaarman/mockito-kotlin
e.g.:
val captor = argumentCaptor<() -> Unit>()
verify(someClass).doSomeThing(captor.capture())
or
val captor: () -> Unit = argumentCaptor()
verify(someClass).doSomeThing(captor.capture())
I tried what #mfulton26 suggested, but was getting an error message saying captor.capture() must not be null. and this was what worked for me.
Declared a member variable captor with #Captor annotation,
#Captor private lateinit var captor: ArgumentCaptor<Callback>
and in my #Test,
verify(someClass).doSomething(capture(captor))
I had this problem just now and solved it with an inline argumentCaptor from mockito-kotlin:
argumentCaptor<String>().apply {
verify(myClass, times(2)).setItems(capture())
assertEquals(2, allValues.size)
assertEquals("test", firstValue)
}
firstValue is a reference to the first captured object.
Source: https://github.com/mockito/mockito-kotlin/wiki/Mocking-and-verifying#argument-captors
Based on mfulton26's answer, i create an example below.
to show how to invoke the captured function or lambda expression.
you need the mockito-kotlin
Assume we have a Class A, it has a suspend function with two higher order function as parameters.
how can we mock the onSuccess scenario and onError scenario
class A {
suspend fun methodB(onSuccess: (ModelA) -> Unit, onError: (ErrorA) -> Unit)
}
Here is the dummy example
// in the unit test class
private val mockClassA = // use annotation or mock()
// decalre the higer oder function capture variables.
private val onSuccessCapture = argumentCaptor<(ModelA) -> Unit>()
private val onErrorCapture = argumentCaptor<(ErrorA) -> Unit>()
#Test
fun testMethodB = testDispatcher.runBlockingTest {
doAnswer {
// on success scenario
val modelA = // get ModelA
onSuccessCapture.firstValue.invoke(modelA) // this line will let the onSuccess parameter been called
// on error scenario
// val errorA = // get ErrorA
//onErrorCapture.firstValue.invoke(errorA)
}.`when`(mockClassA).methodB(onSuccessCapture.capture(), onErrorCapture.capture())
}
Given the following Groovy class:
class MyClass {
def someClosure = {}
def someClosure2 = {}
private privateClosure = {
}
def someVal = 'sfsdf'
String someMethod() {}
}
I need a way to retrieve the names of all public properties that have closure assigned to them, so the correct result for this class would be ['someClosure', 'someClosure2'].
I can assume that all the classes of interest have a default constructor, so if it makes things easier, I could retrieve the properties from an instance via
def instance = MyClass.newInstance()
You can simply check the value of every groovy property:
class Test {
def aClosure = {}
def notClosure = "blat"
private privateClosure = {}
}
t = new Test()
closurePropNames = t.properties.findResults { name, value ->
value instanceof Closure ? name : null
}
assert closurePropNames == ['aClosure']
The private fields are not considered groovy properties, so they won't be included in the results.
If for example I have a class named A. Can I make an object be callable, just like Python does? For example :
def myObject = new A()
myObject()
and that would call some object method. Can it be done?
In Groovy only closures are callable by default. E.g. Classes are not callable out of the box. If necessary you can dynamically add a call method to a type's ExpandoMetaClass to make all instances of that type callable.
Hint: you can try out all code sample using the GroovyConsole
Closures are callable by default in Groovy:
// A closure
def doSomething = { println 'do something'}
doSomething()
// A closure with arguments
def sum = {x, y -> x + y}
sum(5,3)
sum.call(5,3)
// Currying
def sum5 = sum.curry(5)
sum5(3)
To make all instances of a specific type callable you can dynamically add a call method to its meta class:
MyObject.metaClass.call = { prinlnt 'I was called' }
def myObject = new MyObject()
myObject()
If you rather only make a specific instance callable you can dynamically add a call method to its meta class:
def myObject = new MyObject()
myObject.metaClass.call = { println 'Called up on' }
myObject()