Proposal: allowing default values for infix functi...
# language-proposals
j
Proposal: allowing default values for infix functions making it possible to use infix notation almost like a "postfix" notation Use case, when building a DSL that requires "chaining words", like in SQL, the last "word" requires garbage input in order to compile
Copy code
class postgresql(function: postgresql.() -> Unit) {
		
        ....
        
        infix fun postgresql.DESC(value: Unit): postgresql {
			return this
		}
	}

	fun main() {
		postgresql {
			SELECT (
				ListingTable::section, ListingTable::id,
				ListingImageTable::name, ListingImageTable::listingId
			) FROM (
				ListingTable::class
			) LEFT JOIN (
				ListingImageTable::class
			) ON (
				ListingTable::id == ListingImageTable::id
			) WHERE (
				true
			) ORDER BY(ListingTable::modified) DESC Unit
		}
e
What about changing structure to something like (ps: not an SQL expert)
Copy code
postgresql {
   SELECT (ListingTable::section, ListingTable::id, ListingImageTable::name, ListingImageTable::listingId) 
   FROM (ListingTable::class) 
   LEFT JOIN (ListingImageTable::class) 
   ON (ListingTable::id == ListingImageTable::id) 
   WHERE (true) 
   ORDER BY(ListingTable::modified) 
   DESC()
}
j
sure, there are certainly workarounds like changing DESC to an ENUM value or making it a non-infix function call it simply makes for a less nicer DSL maybe a
postfix
/
prefix
keywords allowing you to chain words like that would be very powerful in a DSL context then you never need to hack
infix
with default values
r
For what is worth Scala postfix operators are discouraged and postfix syntax is in general problematic beyond simple use cases https://contributors.scala-lang.org/t/lets-drop-postfix-operators/1457 Additionally in most use cases of a query builder you want that whole thing to be a datastructure potentially to plan analyze or simplify before sending verbatin to a server. These kinds of DSLs can be built with infix notation over the receiver in Kotlin easier https://arrow-kt.io/docs/aql/orderby/ If I were to copy a scala feature to get proper SQL like and DSL clone like langs embedded into Kotlin string interpolators is a much more powerful feature since it would allow you just to express this as:
Copy code
sql"select * from ..."
Then lib authors can tell the compiler what destructuring that string means in terms of types and your types. It’s type safe and users don’t need to learn new programmatic DSLs
👍 2
j
i'm trying to get away from String interpolation with ${}, it's messy and error-prone It's also messy if i want to include if statements in order to include / exclude certain sections of the query Also doing //language=SQL above the String-block, doesn't really work well since you're mixing Kotlin into the Sring See my screenshot below what i've done with String interpolation, ${+table.name} generates fields that i can SELECT / UPDATE on, ${table alias String} allows for aliasing table names in queries, ${table.name} just returns the aliased table name and table.name(value) returns a SQL string where the value is replaced with ? so that i don't expose myself to SQL injection The idea of using chained SQL keywords is to improve this messy string-interpolated DSL How is Scala's String interpolation different from Kotlin? Also, the AQL reference i don't fully understand, is the idea to rather use non-infix calls and have KEYWORD1() KEWORD2() like syntax?
r
yes string replacement with simple templates is messy and error prone but string interpolation as in Scala is powerful because you can create your own interpolators and cooperate with the compiler to destructure the string being able to compile the expression into anything you want https://docs.scala-lang.org/overviews/core/string-interpolation.html
The AQL code is just an example that in Kotlin infix operators allow you to create a DSL like SQL, in this case use to expose a subset of SQL over any data type based on the powers it has to be queried. I think for your use case a typed DSL based on infix combinators based on a sealed hierarchy or a free like structure will give you all you need to construct SQL like queries programmatically in Kotlin without strings or new lang features. Also my personal opinions is that while these DSL that return Unit and mutate inside are nice to use they also allow for incorrect code. What happens if you add the keyword SELECT or WHERE twice in the same scope? I think in this cases correctness is thrown out for ergonomics but when you start composing complex programs inside because queries are built programmatically you are loosing all ability at compile time to know the program is correct and not leading users down a rabbit hole debugging mutation inside there. To build a correct SQL DSL you need to respect the SQL grammar and that will end up looking like a Tree where the operations are leafs like Where, OrderBy etc. You can properly type that today in Kotlin with a sealed class hierarchy, smart constructors for the user API and an interpreter to unfold the query in the target DB dialect.
j
I'm going to play around with Sealed Classes, my initial idea was just to build an "unsafe" SQL DSL that's slightly better than hand-written SQL with String interpolation, but with Sealed Class hierarchies, i can actually make it completely type-safe and make it possible to have compile time checks on the written "SQL"
l
@janvladimirmostert Do you know about SqlDelight?
👍 1
j
yes, does it have postgresql support yet ?
l
It started, along with MySql support.
j
can't remember, it's been a while since i looked at it, but does it support Jasync drivers ?
l
I don't know, but if it doesn't, maybe it's a feature that can be added, and maybe you can even contribute to have it added if it makes sense to have it built-in into SqlDelight main or companion artifact.
j
i would definitely look into that when i get time, similarly with JOOQ, great DSL, but only JDBC support for now i just want to replace a bunch of hand-written SQL queries that's currently using Jasync, very basic queries, but they're pages long and making any changes to them is error prone let me go see if SQL Delight covers all the postgres features i need covered, then maybe it's easy enough to add Jasync support
l
I don't think they have Jasync drivers yet, but why not bring it to SqlDelight? You can start by opening an issue to see if they're open to have such a first-party driver.
Sounds simpler than building a Postgre SQL DSL library plus to me once you factor you still need to work on the "driver".
j
the driver part works in what i have here, there's actually quite a bit of working infrastructure here already was planning to build an unsafe SQL DSL, in other words SELECT SELECT SELECT WHERE FROM would not cause a compiler error, but that won't be much worse that """ SELECT ${...} FROM """ which also doesn't have any query sanity checking i'm going to build something basic enough to just replace these String queries, then maybe put some effort into understanding SQL Delight, maybe contribute to the Postgres parsing and Jasync drivers if they're keen on that, for now my pain point is just getting rid of these horrid string-interpolation queries, even if it means writing code that will be disposed of later
l
SqlDelight embeds the SQL dialects grammar and will prevent compilation on syntax error, and the IDE plugin will also pinpoint the syntax errors. You write SQL, and it does most of the Kotlin for you.
You can also reach the maintainers and part of the community in #squarelibraries although less discoverable to contributors that are more found in GitHub issues.
g
Completely agree with Louis, SQLDelight approach is a few levels better then everything what I tried for sql: raw strings, orm builders, dsl It's so much more natural to use it for SQL, because it's sql + compile time checks
r
@janvladimirmostert An example of typed interpolation in a Lang that is not Scala and wish I had in Kotlin for not just SQL https://twitter.com/zenorocha/status/1303001809829593088?s=19
j
i saw that earlier as well, looks like a very powerful feature!