https://kotlinlang.org logo
#getting-started
Title
# getting-started
r

Ruckus

04/12/2022, 6:47 PM
Is there a good way to have object "inherit" each other? Explanation in thread.
What I'd like is something like
Copy code
object A {
    val a = ...
}

object B : A {
    val b = ...
}

object C : B {
    val c = ...
}

use(C.b)
The best I've come up with is
Copy code
interface A {
    val a: Thing

    companion object : A {
        override val a = ...
    }
}

interface B : A {
    val b: Thing

    companion object : B, A by A {
        override val b = ...
    }
}

interface C : B {
    val c: Thing

    companion object : C, B by B {
        override val c = ...
    }
}
but it's rather tedious.
p

Paul Griffith

04/12/2022, 6:51 PM
inheritance across singletons sounds like sort of an anti-pattern; I could see singletons implementing an interface but I don’t know why you wouldn’t just reference A, B, C individually here
y

Youssef Shoaib [MOD]

04/12/2022, 6:52 PM
Have them be `open class`es with each companion just simply extending its surrounding class without adding anything extra
e

ephemient

04/12/2022, 6:52 PM
Copy code
sealed class A {
    val a = "..."
    companion object : A()
}
sealed class B : A() {
    val b = "..."
    companion object : B()
}
sealed class C : B() {
    val c = "..."
    companion object : C()
}
but I agree with above, this does not seem like a great pattern
🤔 2
🙌 2
r

Ruckus

04/12/2022, 6:54 PM
It's a bit of a weird use case, where the sole purpose of the objects is to bring the elements into scope for a DSL.
@ephemient That's pretty nice, thanks, but unfortunately won't work for my use case, as I want users to be able to "extend" the objects with their own definitions.
y

Youssef Shoaib [MOD]

04/12/2022, 6:57 PM
Maybe consider if context receivers could work here? If you don't have any actual polymorphism (functions overriding parent functions, or functions changing behaviour based on the object) then using a bunch of context receivers could be more elegant
👍 1
r

Ruckus

04/12/2022, 7:00 PM
I guess a bit of context could help: I'm working on a type of CSS generator, where the object inheritance is used to bring some selectors and properties into scope. Something like this:
Copy code
object PlatformStyles {
    val header = cssClass("header")
    val content = cssClass("content")
    val foooter = cssClass("footer")

    val baseColor = CssProperty<CssColor>("base-color")
    val accentColor = CssProperty<CssColor>("accent-color")
}

object LibraryStyles : PlatformStyles {
    val sponsorBox = cssClass("sponsor")
    val sponsorBias = CssProperty<Bias>("sponsor-bias")
}
And it would be used
Copy code
app.style = LibraryStyles {
    header {
        baseColor = color("red")
    }
    content {
        accentColor = color("orange")
        sponsorBox {
            sponsorBias = Bias.Wide
        }
    }
}
👀 1
y

Youssef Shoaib [MOD]

04/12/2022, 7:01 PM
For instance, I'm fiddling with a weird DSL myself currently and I use context receivers to bring a few methods into certain scopes. The usual alternative would be to subclass my DSL class and add those extra methods, but having those methods on a context receiver helps out a lot. What I do is define a new (maybe value) class
MyExtra
and I add the methods I want as extensions on MyExtra but while requiring a context of the normal DSL class. Then simply whenever I want to give the user access to those methods, I put them in a
context(DslClass, MyExtra)
and let the magic happen
e

ephemient

04/12/2022, 7:02 PM
it's not obvious to me those need to be
object
, and even plain old receivers may work if you have a simple setup, e.g.
Copy code
interface A { companion object }
val A.a get() = ...
interface B : A { companion object }
val B.b get() = ...
interface C : B { companion object }
val C.c get() = ...
r

Ruckus

04/12/2022, 7:06 PM
That's an interesting idea. I'll mess around with that, though it would potentially mean uglier imports for the user, and (probably the real issue) the values would get re-created every time they're accessed.
I agree there's no practical reason for there to be objects, I just can't think of a better way to automatically bring them into scope.
@Youssef Shoaib [MOD] The actual DSL is using context receivers extensively, but I don't see how exactly they would help for this part. My concern is for the end user defining the styles. I may just not be entirely understanding your example. Could you elaborate a bit?
y

Youssef Shoaib [MOD]

04/14/2022, 3:54 PM
What I was thinking is you can define classes with the attributes you want the user to provide e.g. header, sponsor-width, etc.and have the objects implement defaults for it. What you can then do is if the user wants to implement their own custom properties they can simply create a class for that, and you, as the library, just takes the user's custom type and passes it along as an extra context to the dsl calls, meaning that instead of inheritance, you use composition. TBH, now that I think about it more, it is more of a superficial difference, so as long as your Dsl design is working so far, then just carry on. I'd be interested to look through your library if you ever publish it on github btw, sounds like there's a lot of room to use some slick DSL tricks with delegates for instance (btw, did you know that local delegated variables can use context receivers? it's a very neat feature and it could help you out here.)