Typesafe DSLs in Java: Part 1 — Typesafe Bytecode

Discussions

News: Typesafe DSLs in Java: Part 1 — Typesafe Bytecode

  1. "Typesafe DSLs in Java: Part 1 — Typesafe Bytecode," by Jevgeni Kabanov (M. Sc., in case you're interested), offers a way to use ASM to build a typesafe engineering DSL as a case study. There's also a followup ("Typesafe ASM — problems solved?") that addresses some of the problems the first post left unaddressed. Jevgeni is scheduled to be speaking at TSSJS in Prague, although he's not on the schedule yet - it looks like a cool session based on the abstract. It's an interesting concept - one of the things that Jevgeni is addressing in his post is the following sort of process, which is an example of a normal fluent interface:Person p=new Person().setFirstName("Jean-Claude").setHair(Hair.BROWN).setHair(Hair.GREY);In this case, you can see where setting the hair color is done twice, which Jevgeni addresses by saying:
    The type you return from your DSL method should allow exactly those operations that are possible with the current state.
    In other words, the setHair method shouldn't return a Person, but a Person implementation for which setHair() isn't valid - which means the above code wouldn't compile. The cool thing here is that Jevgeni's actually producing something to address this sort of problem - in that fluent interfaces are a result of not having good DSLs in the first place. There's a lot of potential here. The idea is that with typesafe fluent interfaces, you can build a domain-specific type of language for Java that are just as powerful as you might find in Scala or Haskell. There's even a project in the works to show all of this in action: flu, hosted on Google Code. It's important: this project is not production-ready, although Jevgeni says that it will be eventually. Some example code using this concept:Person p = new Person(); List> persons = new QueryBuilder(datasource) .from(p) .select(p.name, p.height, p.birthday) .where(gt(p.height, 170)) .find(); for (Tuple3 person : persons) { String name = person.v1(); Integer height = person.v2(); Date birthday = person.v3(); System.out.println( name + " " + height + " " + birthday); }This is cool stuff - even if the SQL parsing isn't directly useful to you, the potential for enforcing type safety and valid operations on fluent interfaces can be a powerful addition to the toolbelt.
  2. To give credit where credit is due -- the typesafe SQL stuff is joint work with Juhan Aasaru and Rein Raudjärv.
  3. Like Linq[ Go to top ]

    This example is amazingly like Microsoft's Linq in C# 3.0, which is a very elegant way to incorporate querying capabilities into C#. Linq let's you do things like this: List customers = GetCustomerList(); var waCustomers = from c in customers where c.Region == "WA" select c; Where waCustomers is some sort of type inferred result set that contains all of the results. In order to get this to work, they had to make some fundamental changes to C# such as type inference anonymous lambdas and lots of syntactic sugar. Is this something that can be done with a typesafe DSL?
  4. Re: Like Linq[ Go to top ]

    This example is amazingly like Microsoft's Linq in C# 3.0, which is a very elegant way to incorporate querying capabilities into C#. Is this something that can be done with a typesafe DSL?
    Not as elegantly, but quite possibly much better than now :) People underestimate how powerful Java can be, it's a question of thinking with it's type system and syntax, instead of complaining about the lacking features.
  5. Re: Like Linq[ Go to top ]

    https://bean-properties.dev.java.net/orm.html
  6. p=new Person().setFirstName("Jean-Claude").setHair(Hair.BROWN).setHair(Hair.GREY);In this case, you can see where setting the hair color is done twice, which Jevgeni addresses by saying:
    The type you return from your DSL method should allow exactly those operations that are possible with the current state.
    In other words, the setHair method shouldn't return a Person, but a Person implementation for which setHair() isn't valid - which means the above code wouldn't compile.
    Really? Is this possible? (I haven't read the article yet. The formatting is painful and I don't like to read code that forces me to scroll back and forth horizontally all the time...) It seems to me that the only way to know that setHair() is no longer valid is to run code inside that object, which therefore needs to be compiled. I can't really see how you could discover this kind of error at compile time... -- Cedric
  7. It seems to me that the only way to know that setHair() is no longer valid is to run code inside that object, which therefore needs to be compiled.
    I think a better example would be this: Person p = new PersonBuilder().setFirstName("Jean-Claude").setHair(Hair.BROWN).setHair(Hair.GREY); The trick is to stage building class PersonBuilder { public PersonHairBuilder setFirstName(String name) { p.name = name; return PersonHairBuilder(p) } } class PersonHairBuilder { public Person setHair(Hair hair) { p.hair = hair; return p; } In a real DSL it would be a builder for each stage that needs to be unique and a toPerson() call in the end. This is the most obvious pattern in the article, really. And the formatting does suck.
  8. I think a better example would be this:
    Person p = new PersonBuilder().setFirstName("Jean-Claude").setHair(Hair.BROWN).setHair(Hair.GREY);
    The trick is to stage building
    class PersonBuilder {
    public PersonHairBuilder setFirstName(String name) {
    p.name = name;
    return PersonHairBuilder(p)
    }
    }

    class PersonHairBuilder {
    public Person setHair(Hair hair) {
    p.hair = hair;
    return p;
    }

    In a real DSL it would be a builder for each stage that needs to be unique and a toPerson() call in the end.

    This is the most obvious pattern in the article, really. And the formatting does suck.
    In theory this is tempting, in practice this is ridiculously tedious, especially with today's tools. Now, if such code can be generated declaratively, then that would be great. The simple example though demonstrates the ability to accomplish that, doesn't expose the difficulties. Imagine 500 different variations of how something can be build. Besides returning a HairBuilder, say out of 500 possibilities, I now reduce the possibilities to 200 based on the current state, so now I must return a type that is a composition of all the 200 variation types. Again, declarative code generation would be nice in this case, but I'm still not convinced that current java is the best way to accomplish a type safe dsl. Ilya
  9. In theory this is tempting, in practice this is ridiculously tedious, especially with today's tools. The simple example though demonstrates the ability to accomplish that, doesn't expose the difficulties.
    Of course -- the example is just that, an example. Check out the bytecode engineering and SQL typesafe DSLs for the real thing (or better yet come to the talk @TSS Prague). It's amazing how much you can do in a typesafe way without any special tools.
  10. I think a more elegant solution, than having to define a class for each stage, involves using interfaces. In essence your builder class should implement various interfaces (one for each stage), and instead of merely returning the builder instance for each method, each method should return one of the interface types. Here's a nice post about this idea: http://laribee.com/blog/2007/07/12/ordered-fluency/
  11. I think a more elegant solution, than having to define a class for each stage, involves using interfaces.
    In essence your builder class should implement various interfaces (one for each stage), and instead of merely returning the builder instance for each method, each method should return one of the interface types.
    Here's a nice post about this idea: http://laribee.com/blog/2007/07/12/ordered-fluency/
    That's true. It would work for SQL part, though not for the bytecode engineering part.
  12. Yes, it's as powerful as what you can build in Scala, but until Java gets closures, type inference, case classes, pattern matching, implicits, operator overloading, for comprehensions etc. it's not nearly as readable as it would be in Scala. I'm not putting the project down, it's great for Java developers to get more powerful tools, but you can't really compare it to what you can do in a functional language like Scala, F# or Haskell.
  13. Yes, it's as powerful as what you can build in Scala, but until Java gets closures, type inference, case classes, pattern matching, implicits, operator overloading, for comprehensions etc. it's not nearly as readable as it would be in Scala. I'm not putting the project down, it's great for Java developers to get more powerful tools, but you can't really compare it to what you can do in a functional language like Scala, F# or Haskell.
    Actually I don't think that in this particular case you can get much more readable in Scala or Haskell. And the things you mentioned are not actually that important, what makes functional DSLs so powerful and what I'm sorely missing is higher order functions. If I could define a function composition function in Java I could define everything :)

  14. Actually I don't think that in this particular case you can get much more readable in Scala or Haskell. And the things you mentioned are not actually that important, what makes functional DSLs so powerful and what I'm sorely missing is higher order functions. If I could define a function composition function in Java I could define everything :)
    Think again, here are some examples of Scala features that really increases readability even in this small example: closures: same as higher order functions type inference: List> persons => val persons operator overloading: gt(p.height, 170) => p.height > 170
  15. closures: same as higher order functions
    There's a distinction. Closures refers first of all to scope binding.
    type inference: List> persons => val persons

    operator overloading: gt(p.height, 170) => p.height > 170
    Well it's not much more readable is it? There are some examples where FP really rocks, like Parsec library in Haskell or Actor library in Scala. Here difference is much milder.
  16. closures: same as higher order functions

    There's a distinction. Closures refers first of all to scope binding.
    True, but that's the interesting part. With closures I mean anonymous function definitions with scoped variable binding and preferably parameter type inference. Higher order functions are trivial to implement in Java, just send an object argument representing the function (this is what Scala does too).
    type inference: List> persons => val persons

    operator overloading: gt(p.height, 170) => p.height > 170

    Well it's not much more readable is it? There are some examples where FP really rocks, like Parsec library in Haskell or Actor library in Scala. Here difference is much milder.
    Well, it's a very small example. With more advance queries the benefits in readability are greater, for example if using for comprehensions to combine queries.