Hacker Newsnew | past | comments | ask | show | jobs | submit | mckinney's commentslogin

Ha! I implemented "concatenative" programming for Java as Binding [1] (or Unit) expressions, but had no idea. Seriously, never came across that term before this post.

The idea with binding expressions is the type of expression A and the type of expression B implement "reactions" with one another in order to form a binding expression when they are lexically adjacent.

65 mph

The type of `mph` defines a post reaction method with the type of `65` as an argument that results in type Rate. As I understand it this is concatenative, right?

Another example:

Money payment = 1.5M USD;

There are tons of these.

Concatenative programming in general feels like it should have a more prominent place in mainstream languages. Just my take.

[1] https://github.com/manifold-systems/manifold/tree/master/man...


If you see units as multiplicative factors:

1.5 USD = 1.5 * USD

65 mph = 65 * mile / hour

you can then use simple algebra to solve unit conversion and many other common deductive reasoning questions ("how many miles per gallon...?"), relying on the units to guide you to the solution.

There's probably a name for this, and it is indeed built into the SI notation (km/h vs mph).


This is already implemented with manifold's unit expressions, but is made richer and more readable via concatenative features. For instance, '1.5 * USD' is not as readable as simply '1.5 USD'. However, unit expressions leverage operators for dimensional arithmetic:

Force f = 5 kg * 9.807 m/s/s; // result: 49.035 Newtons

Area space = (20ft + 2in) * (37ft + 7.5in); // result: 758 37/48 ft²

All unit expressions internally store amounts as SI units, which enables interunit expressions.

Length height = 6 ft + 4 cm; // mix SI and US units

out.println(height.to(ft)); // display any unit

You aren't limited to units. Generally, any concatenative sequence can be implemented. Like ranges:

IntegerRange range = 1 to 5;

Here the `to` identifier's type implements reactions to Number types to produce range types, which enables:

for (int i: 1 to 5) { out.println(i); }

See the manifold project's Unit and Science modules:

Units: https://github.com/manifold-systems/manifold/tree/master/man...

Science: https://github.com/manifold-systems/manifold/tree/master/man...



It is my understanding that the JVM itself uses a stack model at its core.


And end government subsidized corn vis-à-vis ethanol production.


Two funny things: 1) originally environmentalists thought they could make common cause with midwest corn farmers by backing ethanol as a green fuel; that was back in the 80s; 2) for decades now environmentalists having been arguing that we should remove ethanol subsidies because they don't work as promised and have enormous negative externalities of their own. And regardless of the positions of the environmentalists they remain the boogeymen of the right and thus midwestern farmers. Now they, not the farmers, are the ones responsible for the fertilizer runoff and algal blooms. It's almost as if the actual positions of environmentalists were never the issue for those who hate them.


Except, if not for environmentalism, this mess would not exist.


Another funny thing: in your view of the world the entities that made money off this situation -- the farmers and agribusiness -- and the entities that have continuously fought for the ethanol subsidies since they were introduced bear no blame for their negative consequences. The environmentalists, who were their allies initially and have fought them ineffectually now for decades, are entirely responsible. The environmentalists are simultaneously the only people with agency and responsibility in this scenario and largely ineffectual while the farmers and agribusiness are just a force of nature. They bear no more responsibility for their actions than lightning does when it strikes a tree. They are powerful, dominating even, but blameless.


Thank you!

Yes, Manifold already supports Java 15 multi-line strings (aka text blocks) like this:

    var myValue = """
    [>.js<]
      function foo() {
        return "hi";
      }  

      foo();
    """;
You can embed a resource fragment as either a declaration or a value. You use a string to embed a value fragment as with the JS example above. Note the [>.js<] header indicates the resource type for the string, similar to your annotation. As you surmised, an expression such as a string literal cannot be annotated.


Right, but that is just syntax highlighting. This is resolving the content of the string type-safely, at compile-time.


> but is there really a need to have this kind of language interoperability at compile time?

Well, if you want to leverage Java's static type system (and why not?), the answer is, yes. I imagine you'd want type and member references to the other language to resolve statically using the compiler, right? Similarly, why not have the same functionality in your IDE? Plus code completion, usage searching, refactoring?

Now, as I mentioned in an earlier comment, the embedding part of this addresses just a small segment of use-cases e.g., scoped query editing. The vast majority of other cases work directly against resource files, type-safely. Read more about that here:

https://github.com/manifold-systems/manifold


I’m not at all contesting your reasons, which I expect were plentiful enough to build this system. I’m simply saying that if I want to use Java’s static type system, I’ll just write in Java. Perhaps I’m an old codger these days in my ripe early 40s.


> Good IDE lets you move between the resource file and the code with a single click.

Well, not exactly. Most IDEs do NOT support clicking through a generated method call to the corresponding element in a resource file. Manifold is all about that. See it in action: http://manifold.systems/images/graphql.mp4

The Manifold fragments discussed here address only a narrow band of use-cases where embedding the resource improves the dev experience, like with queries and such. It's not for everyone, though.


Hi, you can blame me for this. It's a Java compiler plugin[1], which is similar to an annotation processor, but can hook into the compiler at a much earlier stage, which allows it to contribute to all phases of the compiler, including the Parser phase. The Manifold plugin takes full advantage of this, hence its ability to analyze comments, contribute to bytecode generation, etc.

[1] https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com...


Amazing! Reading the posted link this looks much bigger than embedding, which is crazy impressive btw. Do you anticipate building support for more resources? I notice you have GraphQL support... what about SQL?


So you have a javascript frontend hooked into the java compiler? Or how does your javascript integration work?


At compile-time manifold-js[1] parses JS and generates Java types (stubs), which forward execution to rhino at runtime. Basically, JS seamlessly mapped onto Java's type system.

[1] https://github.com/manifold-systems/manifold/tree/master/man...


But the main idea with this is to avoid the pitfalls of conventional code generation such as GQL Code Generator. See "The Big Picture"[1] in the core docs.

[1] https://github.com/manifold-systems/manifold/tree/master/man...


When I see "no code on disk" I see that as a downside. What happens when this generated code has a bug? How do I set a break point in this code? How do I see the code to know what it is doing?

Extending the compiler is great... until it isn't. Imagine a feature in java the language not working correctly due to a compiler bug. Debugging or working around this issue could be very difficult. With compiler extensions, you are now widening the opportunity for this type of bug to occur.

More principled code generation systems like Racket's macro system do in fact let you expand the code to see what's generated while avoiding having a separate build step, and even have a macro debugger for interactive debugging. Mainstream languages have a ways to go before we get this kind of tooling for language extension frameworks. Until then, I think I prefer having a separate build step with on-disk, visible, and debug-able code.


You can see the generated code in the IDE via Edit | View Java Source when a resource is selected. And you can debug it as well. Sort of the best of both worlds. What makes Manifold most interesting, however, is the entire dev experience when used in the IDE. This short screencast demonstrates some of that: http://manifold.systems/images/graphql.mp4


I definitely agree; the author clearly thought no on-disk produced code was a feature, but it makes me nervous. Similarly, the author then goes on to say how the framework supports circular dependencies between manifolds, or manifolds that each modify one another's types ... and I immediately think that will create debugging nightmares.


In most cases, the devs that should be concerned about debugging generated code are the authors of the generator. If there are bugs in released code, consumers of the API should file against them. In any respect, as I mentioned in a previous comment when a resource is selected you can view its generated source via Edit | View Java Source.


> What happens when this generated code has a bug?

That's a bug in the generator and should be treated as such. Patch or throw out the generator. It's not sustainable to review and patch "generated code", because then it's just "code".

The only reason I need code-on-disk is if I need to generate bindings into multiple languages.


But looking at the generated code, in the specific context where it was produced, is possibly a key step to understanding the bug in the generator. How else are you going to understand how to patch it, or whether the it's bad enough that you have to "throw out the generator" (if you can afford to do that)?


I misspoke when I said "Patch [...] the generator." I meant to say "Have someone else patch it". I meant to imply your time is too valuable to look at the intermediate source.

I'd drive a car to work to save time. If I have to get out and push the car instead, that's happening no more than once. I ditch the car, because it no longer saves me time. Getting out and pushing the car is not going to become part of my commute, and I'm not going to evaluate future cars on how easy they are to push.

You can afford to throw out the generator because it doesn't claim to give you new functionality. It just cuts down on boilerplate. If it can't do that properly, write the boilerplate.


The Manifold project[1] provides string interpolation for Java. You enable it as a Java compiler plugin, it's pretty cool.

[1] https://github.com/manifold-systems/manifold/tree/master/man...


while cool, it's pretty telling that similarly high-level languages like C#, Kotlin, Scala, Python, Swift and Javascript have had this widely-used quality of life for years while Java doesn't


And by telling I think you mean that over time Java has very thoughtfully evolved in a way that respects backwards compatibility, or that once a feature is in the language it'll very likely stay there for the next 25 years. Yes, innovation is very important (and happening faster than ever now with the 6-month release cadence), but a majority of the work isn't in deciding what goes in, it's deciding what stays out and if something does go in how does it satisfy both audiences of "move slow don't break" and "move faster we want features".

A good video on this process: https://www.youtube.com/watch?v=2y5Pv4yN0b0


Thanks. Looks very interesting


It's both. If the popularity of new languages is any measure, static languages have quietly won the static v. dynamic war over the last few years: TypeScript, Kotlin, Swift, etc. Also considering the onslaught of static type checkers and compilers for otherwise dynamic languages such as Ruby, Python, etc. One must conclude static type analysis is everything, or at least it appears to be. IDEs are for the most part measured by how well they incorporate static analysis with features such as deterministic code completion, navigation, usage searching, refactoring, and the like. New IDEs for old static languages are finally arriving such as CLion. Not that IDEs can't be built for dynamic languages, they can, but they tend to be less efficient and less capable because static type analysis is so much more difficult to achieve due to the inherent nondeterminism.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: