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

[Feature Request/Question] Higher-Order Function with Receivers #18

Open
Sintrastes opened this issue Jun 9, 2022 · 1 comment
Open

Comments

@Sintrastes
Copy link

Hi.

First off, this seems like a great project, and I am looking forward to keeping track of it's development.

I was wondering, will this project support higher-order functions with receivers? This seems like it would be very useful for allowing for common types of Kotlin-style DSLs for being defined in Ketolang, is (in my opinion) one of the most distinctive features of Kotlin, and I believe is still compatible with the goals of Ketolang.

One issue I see with this is that in order to be useful, Kotolang would probably have to also support interfaces to allow for useful receivers for builder DSLs. But then again, maybe defining data classes in a "record of functions" manner would be sufficient, e.x.

data class BuilderScope(
    val add: (Int) -> Unit
)

fun sillyBuilder(handler: BuilderScope.() -> Unit): List<Int> {
    // Internal mutable var -- allowed in Ketolang
    var ints = listOf<Int>()
    val scope = BuilderScope { ints = listOf(it) + ints }
    scope.handler()
    return ints
}

val example = sillyBuilder {
    add(1)
    add(2)
    add(3)
}
@artem-zinnatullin
Copy link
Contributor

Hi @Sintrastes, I'll be having a conversation with JB people hopefully soon and will definitely be touching Kotlin DSL syntax support.

Looking at your example what bugs me is that data class has a property of functional type, meaning that it stores not value but a behaviour.

One of the goals of the Ketolang language is to split data from functions, so a functional type should not be stored in data. Benefit that it gives is that Ketolang data types should be serializable w/o additional work from the developer, which is particularly helpful for caching Ketolang execution: function must produce same output for given input, meaning that parameters and return value can be cached — need to be serialized.

For now I'd like to point to Starlark examples (which is the language that inspires Ketolang):

  • rules_java Starlark Rules for building Java code via Bazel build system
  • BUILD file that uses these rules_java

As you can find in these examples there are just function invocations with data types, I do understand that Kotlin DSL is something many people got used to, especially with Gradle build.gradle.kts files, hopefully we'll figure out some consensus on that.


To add to this, there are few issues with Kotlin DSLs that I'm not sure we'll be able to solve:

DSL function can be called multiple times

(Example refers to kotlinx.html HTML)

html {
  body {

  }

  body {

  }
}

Here I've called body twice, that doesn't make much sense and not all DSLs will have meaningful way of handling multiple invocations of DSL builder blocks. And what makes it even weirder is that body could've been a property (see below, so it's an inconsistent design choice up-to developer).

As opposed to this where these are simple function calls with parameters that can be passed once:

html(
  body = body(children = listOf(h1(text = "abc"))),
)

Writing DSL in Kotlin is quite confusing

DSL requires writing functions with receivers A.(B) -> C

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // create the receiver object
    html.init()        // pass the receiver object to the lambda
    return html
}

Even for people with years of production Kotlin use this looks confusing. Ketolang aims to be generic configuration language and many of the Kotlin features I've limited so far are limited for sake of simplicity for people w/o Kotlin experience.

Kotlin DSL allows both properties and functions

Property probably means that a value is stored once, function probably means that value can be stored once or multiple times I guess, but that is all up to DSL developer, one can override property setter and accept multiple property assignments into a combined data type. It's all around confusing I'd say. In HTML example above body could've property, but it is a function, why…? Ketolang can limit DSL syntax, but yeah.

// See https://proandroiddev.com/writing-dsls-in-kotlin-part-2-cd9dcd0c4715
val person = person {
    name = "John"
    dateOfBirth = "1980-12-01"
    address {
        street = "Main Street"
        number = 12
        city = "London"
    }
    address {
        street = "Dev Avenue"
        number = 42
        city = "Paris"
    }
}

Ketolang aims to gently suggest different users to write similar code in similar way, this is why for now interfaces and other OOP features are disabled.

Kotlin DSL requires mutable state

// See https://proandroiddev.com/writing-dsls-in-kotlin-part-2-cd9dcd0c4715
val person = person {
    name = "John"
    address {}
}

class PersonBuilder {
    var name: String = ""
    private val addresses = mutableListOf<Address>()
}

This perhaps can be generically solved by declaring that mutable state in a function, rather than a class, as you cleverly did in your example which I find honestly better than having mutable builder classes. However, I'm not sure this is reusable when you need to share builder like you do for HTML where many tags are compatible with each other and you kind of need to expose builders to each other?


These are my current thoughts on Kotlin DSLs in Ketolang, please post your thoughts and ideas, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants