I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.
Related
I'm trying to get a class similar to this (contrived) example to compile:
class Foo<T> {
val value: T
val condition: Boolean
fun <R> transform(func: () -> R): R {
return if (condition) {
func()
} else {
// Type mismatch: required: R, found: T
value
}
}
}
The transform method can return either value or the result of func(), so T should be assignable to R. Is there a way to express this in Kotlin?
I tried using where T : R, but the compiler doesn't understand that T should refer to the class's T. An extension function could work, but I want to avoid that because it complicates Java interoperability.
You can try this , it works. You need to pass two type params while initializing.
class Foo<T:R,R> constructor(val value: T,val condition: Boolean) {
fun transform(func: () -> R): R {
return if (condition) {
func()
} else {
value
}
}
}
Example:
var s = Foo<String,CharSequence>("12",false).transform {
"as"
}
You pass "12" as string . Transform return "as" value as CharSequence .
Update:
As far as I know, only using extension function might be solve your requirement.
Here is the extension function solution.
class Foo<T> constructor(val value: T,val condition: Boolean){}
fun <T:R,R> Foo<T>.transform(func: () -> R):R{
return if (condition) {
func()
} else {
value
}
}
Example of using extension function solution.
fun main() {
var s1 = Foo(setOf("Hello"),false).transform<Set<String>,Iterable<String>> {
setOf("World")
}
var s2 = Foo(listOf("Hello"),false).transform<List<String>,Iterable<String>> {
listOf("World")
}
}
You just use the type parameter from the class directly. Your method doesn't need to introduce another type parameter:
class Foo<T> {
val value: T
val condition: Boolean
fun transform(func: () -> T): T {
return if (condition) {
func()
} else {
value
}
}
}
I currently face the problem of correctly closing resources that never leave their containing Either.
The relevant code looks something like this:
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Either<SomeError, MyRes> { [...] }
fun extractData(res: MyRes): String { [...] }
fun theProblem(): Either<SomeError, String> {
return createRes()
.map { extractData(it) }
}
What is the most idiomatic way of closing the created MyRes? Closing it before that map prevents extractData from accessing it, and after the map I can't access it anymore via Either's operations. Closing it in extractData severely limits composability.
Currently I have an external List<AutoCloseable> that I iterate over after all the computations, but that can't be the intended way.
I am open to using Arrow Fx (e.g. Resource) if that helps, but I haven't found anything on how to combine Either and Resource in an elegant way.
It's possible to combine the either and Resource safely.
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Resource<Either<SomeError, MyRes>> { [...] }
fun extractData(res: MyRes): String { [...] }
suspend fun solution(): Either<SomeError, String> = either {
createRes().use { either: Either<SomeError, MyRes> ->
val res = either.bind()
val string = extractData(res)
// call other Either code + `bind()` safely here
[...]
} // <-- MyRes will automatically close here
}
If in this code you encounter Either.Left and you call bind() on it the Resource will first close, because we jump outside of use, and then either will return the encountered Either.Left.
One possible solution I found was wrapping the block passed to map:
fun <B : AutoCloseable, C> andClose(f: (B) -> C): (B) -> C =
{ b: B -> b.use { f(b) } }
fun theProblemSlightlySolved(): Either<SomeError, String> {
return createRes()
.map(andClose { extractData(it) })
}
using kotlin, having code
fun fetchRemoteDataApi(): Single<RemoteDataResponse> = networkApi.getData()
// it is just a retrofit
#GET(".../api/getData")
fun getData() : Single<RemoteDataResponse>
fun mergeApiWithDb(): Completable = fetchRemoteDataApi()
.zipWith(localDao.getAll())
.flatMapCompletable { (remoteData, localData) ->
doMerge(remoteData, localData) //<== return a Completable
}
the code flow:
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache() //<=== would like do some inspection at this level
PublishSubject.create<Unit>().toFlowable(BackpressureStrategy.LATEST)
.compose(Transformers.flowableIO())
.switchMap {
//merge DB with api, or local default value first then listen to DB change
mergeApiDbCall.andThen(listAllTopics())
.concatMapSingle { topics -> remoteTopicUsers.map { topics to it } }
}
.flatMapCompletable { (topics, user) ->
// do something return Completable
}
.subscribe({
...
}, { throwable ->
...
})
and when making the call
val mergeApiDbCall = mergeApiWithDb().onErrorComplete().cache()
the question is if would like to inspect on the Singles<RemoteDataResponse> returned from fetchRemoteDataApi() (i.e. using Log.i(...) to printout the content of RemoteDataResponse, etc.), either in got error or success case, how to do it?
/// the functions
fun listAllTopics(): Flowable<List<String>> = localRepoDao.getAllTopics()
// which a DAO:
#Query("SELECT topic FROM RemoteDataTable WHERE read = 1")
fun getAllTopics(): Flowable<List<String>>
///
private val remoteTopicUsers: Single<List<User>>
get() {
return Single.create {
networkApi.getTopicUsers(object : ICallback.IGetTopicUsersCallback {
override fun onSuccess(result: List<User>) = it.onSuccess(result)
override fun onError(errorCode: Int, errorMsg: String?) = it.onError(Exception(errorCode, errorMsg))
})
}
}
You cannot extract information about elements from the Completable. Though you can use doOnComplete() on Completable, it will not provide you any information about the element.
You can inspect elements if you call doOnSuccess() on your Single, so you need to incorporate this call earlier in your code. To inspect errors you can use doOnError() on both Completable or Single.
I'm having a lot of difficulty figuring out a good way to coordinate using RxJava along with the arrow-kt Either and Option types. I have two methods that both return Single<Either<ApiError, Option>
class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError
fun loadFoo(): Single<Either<ApiError, Option<Foo>>> {
...
}
fun loadBar(qux: Qux): Single<Either<ApiError, Option<Bar>>> {
...
}
The goal is to return the result of loadBar(Qux) in an RxJava Single as the type Either<ApiError, Option<Bar>>.
The complication comes from the fact that the qux parameter required by loadBar() is retrieved from the data emitted by the Single returned by loadFoo() (Qux is a property of Foo with the type Option<Qux>).
Desired outcome:
Any ApiErrors that occur get passed to the Single's subscriber in Either.Left
If both loadFoo() and loadBar() return Some, that value should be returned in the composed Single as Either.Right
If either loadFoo() or loadBar() return None, the expected result is Either.Right(None)
I tried a couple things. This first example works, but the resulting code is hard to read because of a bunch of nested folds, as well as intermixing of RxJava and Either/Option operators.
fun loadBarFromMaybeFoo(maybeFoo: Option<Foo>): Single<Either<ApiError, Option<Bar>>> {
return maybeFoo.flatMap { foo -> foo.qux }
.map { qux -> loadBar(qux) }
.getOrElse { Single.just(Either.Right(Option.empty())) }
}
fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
return loadFoo()
.flatMap { maybeFooOrError ->
maybeFooOrError.fold(
{ error -> Single.just(Either.Left(error)) },
{ maybeFoo -> loadBarFromMaybeFoo(maybeFoo) }
)
}
}
The second thing I tried was to use arrow's rxjava observable comprehensions. But couldn't quite figure out how to get this to return Single<Either<ApiError, Option> in the end.
fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
return SingleK.monadDefer().bindingCatch {
val maybeFooOrError: Either<ApiError, Option<Foo>> = loadFoo().k().bind()
val maybeQuxOrError: Either<ApiError, Option<Qux>> = maybeFooOrError.map { maybeFoo ->
maybeFoo.flatMap { it.qux }
}
// return type is Either<ApiError, Option<Either<ApiError, Option<Bar>>>>
// desired return type is Either<ApiError, Option<Bar>>
maybeQuxOrError.map { maybeQux ->
maybeQux.map { qux ->
loadBar(qux).k().bind() // this part doesn't seem good
}
}
}.value()
}
Any help/advice on how to solve this or restructure the data types to make it easier would be much appreciated! Still pretty new to many functional programming concepts.
If I were you, I would consider simplifying your return types and not use Either in the context of a Single, as Single already can emit an error. So in the end, instead of flat mapping over an Either<ApiError, Option<Bar>>, you could only work with an Option<Bar>, and handle the errors in the RxJava chain. Something like:
class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError
fun loadFoo(): Single<Option<Foo>> {
// in case of an error, this will return Single.error(ApiError(...)) if ApiError extends Throwable
// otherwise make it extend it or just wrap it into something which is a Throwable
}
fun loadBar(qux: Qux): Single<Option<Bar>> {
// same as above
}
fun loadBarFromFooOption(maybeFoo: Option<Foo>): Single<Option<Bar>> {
return maybeFoo.flatMap { foo -> foo.qux }
.map { qux -> loadBar(qux) }
.getOrElse { Single.just(Option.empty()) }
}
fun doStuffToGetBar(): Single<Option<Bar>> {
return loadFoo().flatMap { fooOption -> loadBarFromFooOption(fooOption) }
}
// somewhere else
doStuffToGetBar().subscribe({ barOption -> /* ... */ }, { error -> /* ... */ })
This declaration works, but is not the most beautiful code. Is there a way to return functions less ugly? I tried (s: String) -> writer.println(s) but this didn't work.
val writeStuff: (PrintWriter) -> (String) -> Unit = {
val writer = it
val f: (String) -> Unit = {
writer.println(it)
}
f
}
PrintWriter("test").use { writeStuff(it)("TEST") }
EDIT: a bit more concrete example:
val writeStuff: (PrintWriter) -> (String) -> Unit = { writer ->
{ writer.println(it) }
}
val sendStuff: (Any) -> (String) -> Unit = { sender ->
{ sender.equals(it) }
}
#Test fun test1() {
val li = listOf("a", "b", "c")
val process: List<(String) -> Unit> =
listOf(writeStuff(PrintWriter("a")), sendStuff(Object()))
process.map { li.map(it) }
}
First, you can simplify your code using lambda syntax with explicit parameter and inlining val f:
val writeStuff: (PrintWriter) -> (String) -> Unit = { writer ->
{ writer.println(it) }
}
But since Kotlin supports local function declarations, you can even make writeStuff a local fun instead of a val.
This would lead to the following code:
fun writeStuff(writer: PrintWriter): (String) -> Unit {
return { writer.println(it) }
}
Or, using the single expression syntax,
fun writeStuff(writer: PrintWriter): (String) -> Unit = { writer.println(it) }
The usage, however, will be the same:
PrintWriter("...").use { writeStuff(it)("...") }
I stumbled across this question while trying to figure out how to return a Function (the java interface) in Kotlin. While this doesn't directly answer the question, hopefully it'll help someone else who has the same query:
override fun myFun(param1: Object): Function<in Object, out String?> {
if (!param1.meetsCriteria())
return Function { obj -> null }
return Function { obj ->
"success"
}
}
In this case, I was overriding a method in a java interface that required me to return a Function instance. (Note that since the param is not used in my particular implementation above, I could remove it and just have the return result. eg return Function { null })
Edit: After some research, it turns out Kotlin covers this subject with their discussion on "SAM (single abstract method) conversions" here and here, though it may not be the most intuitive thing to look up when figuring out how to return Functions.