1 of 59

Tagless final

Abstraction without guilt

habla.dev

urjc.es/etsii

Juan M. Serrano (@juanshac)

Habla Computing

Universidad Rey Juan Carlos

2 of 59

To abstract, or not to abstract

DAO

Quoted DSL

Tagless Final

Repository

MTL

Icons made by Freepik from Flaticon

3 of 59

Efficiency vs. Abstraction

Plain SQL

Abstractions

Less code

Easy to learn

Efficient

Tons of boilerplate

Rocket science

Inefficient

Modularity

Evolvability

Idiomatic code

4 of 59

Tagless-final, by Oleg Kiselyov et al.

5 of 59

Tagless-final, by Oleg Kiselyov et al.

6 of 59

Tagless-final, or abstraction without guilt

Tagless-final

DSLs

Reusability

Evolvability

Idiomatic code

Less code

Easy to learn

Efficient

7 of 59

What we will do?

Variations on a theme of Language-Integrated Query

8 of 59

Which are the country capitals whose population is larger than 8.000.000 million?

9 of 59

Outline

VARIATION 1 - Plain SQL

VARIATION 2 - In-memory HOFs

VARIATION 3 - Repository pattern

VARIATION 4 - MTL-based repositories

VARIATION 5 - Quoted DSLs

VARIATION 6 - Tagless-final DSLs

10 of 59

VARIATION 1. Plain SQL

11 of 59

VARIATION 1. Plain SQL

Query0

[(String, String)]

ConnectionIO

[List[(String, String)]]

IO

[List[(String, String)]]

List[(String, String)]

JDBC program

IO program

Id program

12 of 59

Efficient

Not modular

Lack of unit testing

Not idiomatic

1

2

JDBC

In-memory

13 of 59

VARIATION 2: In-memory HOFs

Inmutable (flat) data model

(alternatively, hierarchical: optics)

14 of 59

VARIATION 2: In-memory HOFs

Comprehensions as a query language

15 of 59

VARIATION 2: In-memory HOFs

Queries can be modularised

16 of 59

VARIATION 2: In-memory HOFs

Queries can be (unit) tested

17 of 59

1

2

3

Repository pattern

JDBC

In-memory

18 of 59

VARIATION 3: Repository pattern

REPOSITORIES

19 of 59

VARIATION 3: The repository pattern

Scala naïve

REPOSITORIES

20 of 59

VARIATION 3: The repository pattern

REPOSITORIES

...

21 of 59

VARIATION 3: The repository pattern

Scala naïve

LOG: execute <unnamed>: select code, name, capital from country

LOG: execute <unnamed>: select id, name, countryCode, population from city where id = $1 DETAIL: parameters: $1 = '1'

LOG: execute <unnamed>: select id, name, countryCode, population from city where id = $1

DETAIL: parameters: $1 = '5'

LOG: execute <unnamed>: select id, name, countryCode, population from city where id = $1

DETAIL: parameters: $1 = '33'

...

LOG: execute <unnamed>: select id, name, countryCode, population from city where id = $1

DETAIL: parameters: $1 = '4074'

QUERY AVALANCHE! or N+1 queries problem

22 of 59

VARIATION 3: The repository pattern

Spring data + Hibernate

REPOSITORIES

23 of 59

VARIATION 3: The repository pattern

Spring data + Hibernate

encode the query

in the method name!

Ad-hoc query

(suffers from avalanche in the query result!)

Ad-hoc query

(no avalanche at all!)

24 of 59

1

2

3

Repository pattern

4

MTL-based

Repositories

JDBC

In-memory

25 of 59

VARIATION 4: MTL-based repositories

Towards MTL

Computation type

(list-like)

26 of 59

VARIATION 4: MTL-based repositories

Towards MTL

Imperative API

27 of 59

Stream-based, JDBC-level, doobie instance

Towards MTL

Previously,

synchronous

Now, declarative

28 of 59

And, we can get rid of mocking! :)

State-based transformations

29 of 59

However … did we solve our performance problem?

vs.

30 of 59

By no means! Same N+1 problem

vs.

LOG: execute <unnamed>:

select C.name, X.name

from city as C, country as X

where C.id = X.capital and C.population > 8000000

LOG: execute <unnamed>: BEGIN

LOG: execute <unnamed>/C_1: select code, name, capital from country

execute <unnamed>/C_2: select id, name, countryCode, population from city where id = $1

DETAIL: parameters: $1 = '1'

LOG: execute <unnamed>/C_3: select id, name, countryCode, population from city where id = $1

DETAIL: parameters: $1 = '5'

LOG: execute <unnamed>/C_4: select id, name, countryCode, population from city where id = $1

...

31 of 59

1

2

3

Repository pattern

4

MTL-based

Repositories

JDBC

In-memory

32 of 59

33 of 59

VARIATION 5: Quoted DSLs to the rescue!

34 of 59

Inspired the development of the Scala library ...

35 of 59

Program your query as if you were

using case classes …

Macros

LOG: execute <unnamed>:

select C.name, X.name

from city as C, country as X

where C.id = X.capital and C.population > 8000000

36 of 59

1

2

3

Repository pattern

4

MTL-based

Repositories

Quoted DSLs

QUEΛ

Tagles-final

DSLs

5

6

JDBC

In-memory

37 of 59

VARIATION 6: Tagless-final

QUEΛ

38 of 59

Forget about the Scala AST, let’s make our own

Base types (integers) and ops (>, ==)

Optional values and ops (exists)

Product types

World model

Multiset-comprehensions (flatMap, pure, filter)

39 of 59

DSLs embedded as type (constructor) classes

World DSL

Syntax

Type system

(phantom types)

Semantics

40 of 59

Forget about the Scala AST, let’s make our own

Multisets (with comprehensions) DSL

Again, phantom types

Semantics

41 of 59

And we can now write our generic query

… although it’s pretty ugly

42 of 59

With some sugar, we recover the same syntax of List-comprehensions

QUEΛ

43 of 59

Standard semantics: suitable for unit testing

Identity representation,

no phantom types

44 of 59

Standard semantics: suitable for unit testing

Alternatively, we may have used

Repr[T]=StateT[List, World, T]

45 of 59

Standard semantics: suitable for unit testing

46 of 59

Please, show me some semantics that could not be given for MTL-based repos!

type F[T]=String

What about a pretty-printer?

47 of 59

We can’t give a Monad instance for a constant functor :(

How do we obtain an `A`?

How do we convert `A` into `String` (no `Show` instance!)

48 of 59

But we can implement the pretty-printer in tagless-final!

We don’t need an `A`, but a

representation of `A`!

For our purpose,

a variable xi

49 of 59

Pretty-printing in action!

Scala compiler desugars for-comprehensions into flatMap, filter and map, and the Scala run-time desugars the “macro” flatMap into from, etc.

50 of 59

But … what about generating SQL!?

Query0

[(String, String)]

ConnectionIO

[List[(String, String)]]

IO

[List[(String, String)]]

List[(String, String)]

JDBC program

IO program

Id program

MTL-based

Monad, FunctorFilter, WorldModel

Repo WorldModel

Tagless-final DSLs

QUEΛ, WorldModel

51 of 59

Generating SQL from a “normalized” expression is easy!

SELECT C.name, X.name

FROM city as C, country as X

WHERE C.id = X.capital and C.population > 8000000

FROM

WHERE

SELECT

52 of 59

But the for-comprehension query is rather messy :(

53 of 59

We need first to implement a “normalizer”, then the actual query generator

Query0

[(String, String)]

Tagless-final Repr

QUEΛ, WorldModel

Normalized

[Repr, List[(String, String)]

54 of 59

The normalizer re-associates left-binds, much like in the Free monad

Syntactic

normalization

Semantic normalization

55 of 59

Last, we generate the doobie Query0 from the normalized expression

56 of 59

And we can generate and execute the optimum SQL query!

res26: Fragment = Fragment("select x1.name, x0.name from city x1, country x0 where x0.capital = x1.id and x1.population > 8000000")

57 of 59

1

2

3

Repository pattern

4

MTL-based

Repositories

Quoted DSLs

QUEΛ

Tagles-final

DSLs

5

6

JDBC

In-memory

58 of 59

Conclusion

  • Do you want to express queries modularly, and yet generate the most efficient queries? Tagless-final DSLs or Quoted DSLs
  • Tagless-final is not MTL: computations vs. representations (including computations)
  • Do you like domain-driven development, hexagonal architectures, etc.? Learn tagless-final DSLs!
  • No free-lunch: after all, we had to implement a compiler. Be ready to invest time to learn the technique and face unexpected challenges!
  • Quoted DSLs reuse the Scala AST, which is great! We had to define from scratch DSLs for base types, options, tuples, comprehensions, etc. On the other hand, we have more control over our syntax
  • Future steps: No-SQL interpreters, Staging (generating Scala code), etc.

59 of 59

Thanks for your attention!

habla.dev

urjc.es/etsii

juanmanuel.serrano@habla.dev (@juanshac)