Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shouldn't DSL functions be inline? #67

Closed
Miha-x64 opened this issue Dec 28, 2017 · 10 comments
Closed

Shouldn't DSL functions be inline? #67

Miha-x64 opened this issue Dec 28, 2017 · 10 comments
Assignees

Comments

@Miha-x64
Copy link

Is it necessary to have an anonymous class per DSL lambda?

image

@cy6erGn0m
Copy link
Contributor

Originally it was planned to have them inline however it was inefficient in old kotlin-js compilers. Need to be investigated again

@soywiz
Copy link
Contributor

soywiz commented Feb 1, 2018

+1 Not just because of anonymous classes/runtime performance, but making them inline would enable suspend calls inside the DSL which is not possible right now. So it is not prohibitive for the JS Backend now, it would be a really nice improvement :)

@Miha-x64
Copy link
Author

Miha-x64 commented Feb 2, 2018

While writing DOM — may be. While streaming (on JVM) — no! First fetch data, next return HTML,
so nothing will fail in DSL with mangled stack trace.

@orangy
Copy link
Contributor

orangy commented Feb 2, 2018

I agree with @Miha-x64 on data first. However, making it all inline might explode the size of bytecode generated. If it could be an option…

@soywiz
Copy link
Contributor

soywiz commented Feb 2, 2018

I see your point on data first, and makes sense.

Now let me explain to you a use case based on my experience and with a example/rationale behind it.
It doesn't mean it has to be a better approach, or that it do not have flaws, but just consider it.

Suppose I'm creating a CMS, or a plain website. That website has pieces that I can put in different places
and conditionally: for example A/B testing or based on user preferences. Also consider that the amount of
html generated for those blocks could be potentially big and that we want to cache database queries and that we
want to prevent unnecessary database queries.

Now consider the following reusable piece of code:

suspend fun Tag.lastPostsInCategory(category: Category) {
    cache("last-posts-${category.name}") { // suspend call (inmemory, redis, memcached, ...)
        h1 { +"Last posts" }
        ul {
            for (post in category.lastPostsInCategory(category)) { // suspend call (uncached database access)
                li { 
                    a(PostRoute(post.name)) { +post.title }
                }
            }
        }
    }
}

Where cache block is just computed on cache-miss based on a key, and computes a plain string without having to generate objects per each tag for each requesst,
and that could provide (somehow) a graceful fallback like generating "Data not available" just for that block instead of a full 500 Internal Server Error.

You can always compute what is required and what is not before html rendering. But you have to do that explicitly while here it is just natural.
The data is computed just when required and lazily. You don't even have to have all that data in memory while doing the request, but potentially
just one row at a time. It just requires one kind of cache. Instead of potentially having to cache that whole query and later that html too if you
want to prevent that object creation.

Obviously this is just an idea. I have been coding a lot of years in PHP, and maybe this approach has too many flaws. But wanted to share anyway so you can consider it :)


In a plain MVC the controller usually grabs data and passes it to the view. But sometimes, you just compute more data than required, and that doesn't allow to compute it lazily. I worked for a big PHP company a lot of years ago and I have seen it. At least, they, computed more than required. Also since some people worked on views and other in controllers, that was pretty common.

Some years later I tried an approach where views were the ones that lazily requested what it needs. I did this https://github.com/soywiz/atpl.js and created a couple of sites with that approach.
That's not MVC, but after trying it I found it pretty convenient. Also using plain files for html views allows to generate less objects per request. Since it treats a whole chunk of html as a single string. But you lose static typing, assisted autocompletion and typesafety. So I thought about this approach: simple, asynchronous, lazy, typesafe, cacheable to reduce object creation. And it is pretty appealing to me at least for my personal projects.

@spand
Copy link

spand commented Feb 15, 2018

Sorry if I am being dense but what is the bytecode size difference of inlining the code? Isnt it only these functions that need to be inlined?

  • fun <T, C : TagConsumer<T>> C.div(classes : String? = null, block : DIV.() -> Unit = {}) : T = DIV(attributesMapOf("class", classes), this).visitAndFinalize(this, block)
  • fun <T : Tag, R> T.visitAndFinalize(consumer: TagConsumer<R>, block: T.() -> Unit)

I havent performed any tests but intuitively that doesnt seem like much. Especially when taking into account the overhead of all those class files.

@spand
Copy link

spand commented Feb 22, 2018

I performed a quick test with the test classes in module kotlinx-html-jvm.
With noninline methods:

spand@DESKTOP MINGW64 ~/IdeaProjects/kotlinx.html/jvm/target/test-classes/kotlinx/html/tests (master)
$ du -sh .
860K    .

I added inline modifiers to consumerBuilderShared, htmlTagBuilderMethod and htmlTagEnumBuilderMethod:

spand@DESKTOP MINGW64 ~/IdeaProjects/kotlinx.html/jvm/target/test-classes/kotlinx/html/tests (master)
$ du -sh .
196K    .

Unless I have made a mistake or we need to count size some other way then it seems like a pretty nice win.

@elizarov
Copy link
Contributor

I've recently stumbled upon the need for inline DSL functions, too, while trying to naively invoke a suspending function multipart.readPart() from inside of HTML builder block.

@LouisCAD
Copy link

LouisCAD commented Nov 8, 2018

@orangy

However, making it all inline might explode the size of bytecode generated. If it could be an option…

If the inline DLS functions delegate most of their implementation to non inline functions as to have minimum inline code apart from the passed lambda, the bytecode size might not explode that much, and could even shrink as inline generates less classes and methods.

@spand
Copy link

spand commented Apr 3, 2019

What are the outstanding issues to making a decision on this ?

@spand spand mentioned this issue Jan 14, 2020
@e5l e5l closed this as completed Jun 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants