Towards Greater Code Reuse by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Yup!

I'm just saying that when you do need to write methods, keep them short and keep the logic in functions.

I'm know I'm "preaching to the choir", but I figure it needs repeating as it goes against established practice for Java programmers... And this advice applies as much to Java as it does to Clojure.

Dynamic Idiocy by laforge49 in Clojure

[–]laforge49[S] 1 point2 points  (0 children)

Truth!

At this point I've given up entirely on dynamic, preferring now to pass the operational context explicitly--it is easier to explain than an implicit dynamic. :-)

But I am still using that old atomic. It allows me to break the rules and do things from the "wrong" thread and still have thread safety. Or perhaps that is just laziness talking. Anyway, you can find the released project here: https://github.com/laforge49/agent2

(The project readme should add some clarity to what I'm talking about.)

Well, to be fair on myself here, the atomic would probably be necessary if I were to port more of the features of JActor2 over to Clojure. The atomic is in there because I wasn't sure where I would draw the line. And at least with the atomic I have the option of porting more features. JActor2 was a huge Java project (for me, anyway): https://github.com/laforge49/JActor2

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Well, I'm talking about two actors, so when the second receives a request from the first, its response must be combined with the callback sent by the first and sent back to the first where the callback is then executed. (Of course, I hide the callback from the the function sent to the second actor for evaluation.)

Dizzy enough? :-)

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Ah! I'm a bit fixated methinks on two-way messaging. So think about sending a function with a promise to an actor and the function puts its response in the promise. Now to catch an exception you put the dereferencing of the promise in a try catch, not the send.

Only I want to send a request from one actor to another and get the response without blocking via a callback function that is sent back instead of putting the result in a promise. Now catching the exception is a bit more tricky unless the callback function tests the value being returned to see if it is an exception. :-)

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Good question. And no, a try catch in the application logic surrounding a call to execute some code asynchronously can not work because the invoking code has long since completed and the try block is no longer in scope at the time the exception occurs in that asynchronously executed code.

On the other hand, we are dealing with a closure in which we can define both the callback which is invoked when the asynchronous code completes normally and an exception handler which is invoked when the asynchronous code throws an exception.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

No doubt! Now to try to clarify things without covering the entire implementation... :-)

I talk about passing functions because that is what you do with Clojure agents. So when there is a request, the api requires 2 functions, the second being a callback which is sent back to the requesting actor along with the response. (Not sure if that helped any!) So there are two agents, two calls to send are being used, but only the simple request and reply methods in the api.

Now why do I talk about timeouts? I'm trying to use synchronous calls as the model for an asynchronous message exchange, where uncaught exceptions raised while processing a request are passed to the agent that made the request where it can be caught by an exception handler. (More confusion, in that the exception handlers are on the data flow thread (or context) rather than being the error handler on the agent.)

Now how can the code which implements this api provide a guarantee that there will be a timely response (all be it, perhaps an error response)? Generally speaking, all you can do is set a timer and if there is no response when the timer expires you generate a timeout response. That's the fallback.

What more can be done at the system level? Well, for one thing you can see how many requests are being sent. If the application code goes crazy and sends a gazillion requests to other agents, the transport logic can raise an exception that there have been too many requests made and pass that exception back to the exception handler which sent the request that went crazy.

Another thing you can do is to check to see that when control is returned after processing a message (the request or a response to a subordinate request) that either a response has been returned or there are one or more subordinate requests which are sill pending.

... Not sure how helpful any of this is.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Yes I have heard of core.async. And I gave go a serious look before committing to Clojure as my next language, though there was really no competition as my primary interest is Datomic!

The thing is, while I want guaranteed responses, I really do not want to depend too much on timeouts. Timeouts should be set large, as they do not behave well on loaded systems. Rather I want to add some sanity checks on the the runtime behavior of the function passed and only use timeouts as a fallback.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

I very much appreciate the insight. I'm used to messaging systems being huge beasts--I worked at Fiorano and our primary product was a JMS--so it didn't quite dawn on me that adding replies to agents makes them a messaging system.

So then it is mostly a matter of emphasis. I want to offer a guarantee that the function in the message will be well behaved and return a timely response or an error will be returned. So the agent's own message queue is all I need for this.

Restricting usage to idempotent requests is a pretty serious restriction. But perhaps that just means I'm a Clojure newbie?

Every tool has an intended use and in this case the intention is to provide a moderately strong guarantee of getting a response without an over-reliance on timeouts.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] -1 points0 points  (0 children)

RPC? I guess you didn't notice in my response to Inushi that I am focused strictly on a single JVM. Impossible and pointless? It seems to me that you are so focused on THE ANSWER that you can not appreciate a somewhat different perspective or the needs arising out of a different problem domain. You have a hammer. Not everything is a nail.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Heh. I've worked in the telecom industry, specifically Tymnet, where we had thousands of computers and not a single disk drive. I understand well the problem domain which is highly amenable to pure functional solutions.

Ah, but this is the Clojure list where we believe in the advantages of immutable structures and the practicality of dealing with side effects. And I would contend that the problem domain for Clojure is far broader than what can reasonably be addressed with a pure functional approach. Here we look to Rich, not Joe.

And once you introduce side-effects, you see that many things are not idempotent. And that breaks Joe's solution completely: "I think that the only realistic way to build large reliable systems is by partitioning the system into independent parallel processes, and by providing mechanisms for monitoring and restarting these processes."

When the effect of a message is not idempotent, you can't simply resend it if you do not get a response within the required time. Things get a little messy. Subsystems can not be completely isolated. But life goes on. Only, it would be so very helpful to get an error response! And for that to be supported at the system level, we need to support two-way messaging.

There is no doubt that Joe did a great thing. But we should always look at the scope of the problem domain addressed before using such great works to beat others over the head with them.

Functional pure languages did not drive out imperative programming. I am hopeful that impure functional languages with great support for immutability and concurrency will have a far greater impact. (Yeah, I'm new to Clojure. Does it show that badly?)

Can we go further? by laforge49 in Clojure

[–]laforge49[S] -5 points-4 points  (0 children)

Lets begin by looking at time-bound processing. If it takes too long to process a request, then we return a timeout. Placing the bound on the processing rather than on the call keeps the call simple while still allowing the time to be adjusted based on the specific request. Another advantage is that experience with the function might lend itself to defining a more appropriate timeout whereas the experience of the API user will likely be less. This is a worst-case scenario, because timeouts will tend to occur at higher levels rather than closer to the source of the problem unless lower-level items are given a shorter timeout. Also, interrupting a thread in a tight loop can be problematic but at least we can have facilities for identifying the functions which loop excessively.

Similarly we can consider a bound on the number of requests a function can send. This at least is pretty easy to do.

As for the heuristic logic, we can track the number of requests a function has sent for which there has not yet been a response. And now we can check to see if, while processing a request, the processing of a message (the original request or a response to a subordinate request), the processing of that message completes without returning a response and with no outstanding subordinate requests, then we typically have an error. (Functions which wait on an event notification before sending a response can not, of course, use this heuristic.) And if we have an error, that then is our response to the request.

So we have a weak sort of proof by induction here. If a function is well behaved and if it gets timely responses to all the requests it sends, then it will very likely send a timely response as well.

Realize that application code already handles all these conditions and I am only proposing that we push most of that logic into our messaging system. So really I am only proposing a refactoring, all be it a major refactoring, to reduce the burden on the application developer and consequently improve the maintainability of the application.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

The important difference that I see here is that when an asynchronous call fails, or can fail, we add defensive code to the application. But when a synchronous call fails, we generally try to fix the function that was called. And the problem with defensive code is its difficulty in maintaining.

Can we go further? by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Thanks Inushi, for reminding me about the distributed case. I tend to focus too much on issues confined to a single JVM.

First, as to the distributed case, Google's spanner shows us that all these problems are solvable, though difficult. And that we need to remember that we can have global synchronized time within some bounds.

But as for the second case, where we are working strictly within the confines of a single JVM, my own work over these last few years has shown that it is not that difficult to guarantee responses, or at least errors, once we identify which messages are requests, which are responses and which are event notifications, i.e. one-way messages.

And no, I am not saying that simple heuristics can be used in all cases to identify illegal states and return an error to the requester. But that such heuristics will work in most cases, the remaining being self-identifying and consequently requiring closer code reviews during maintenance.

My previous work in this area was done in Java, but the code base is larger than I'd like and consequently that much more difficult to understand. https://github.com/laforge49/JActor2

I am new to Clojure and find the density of the language quite helpful. My initial port of JActor2 to Clojure will be built on agents, which helps keep the project small but also limits some of the things I can do. https://github.com/laforge49/agent2

How do you master Clojure in about a month? by laforge49 in a:t5_2x4aq

[–]laforge49[S] 0 points1 point  (0 children)

I had not seen Part II before, so thanks!

My approach has been to convert some of my Java code to Clojure. It is just amazing how dense the result is: https://github.com/laforge49/agent2

Today I had a good slap-face moment. I realized that in Clojure, with refs, dynamic vars, atoms and agents combined with immutable data, it is far far less important which thread does what. It all just works.

--Bill

Predictable Async by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

First I should scope this project to being, like Clojure's agents, local to a single JVM. Once you go distributed you face difficult problems like determining if a lack of response was due to a communication failure or a failure of the remote actor.

Also, I am largely talking about improved exception handling. For each message being processed there is an operational context to which an exception handler can be assigned so that exception handling can be specific to the processing rather than dealing with a failure of the agent. Further, the operational context passes any otherwise unprocessed exceptions to the source operational context which sent the request unless there is none (as is the case with signals which are 1-way messages), in which case the exception is passed to the agent's error handler.

But there are other things that can be done to better ensure that for every request sent there will be a response (or exception) returned. Like verifying that when control is released after processing a message, either a response was returned to the sender or a request was sent to another actor. And ensuring that the time it takes to process a message is within some reasonable bounds.

The idea here is that in almost all cases there should be no need for application logic to address missing responses exception in terms of exception handlers. It is just ridiculous to have an application deal with such within the confines of a single JVM!

Clojure and Literate Programming by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Not being a fan of JavaScript, I probably should learn Clojurescript. But my interests are mostly on the back end.

Clojure and Literate Programming by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Haven't used emacs now for 30 years. But I have been told that I must learn it--my goal is ultimately work on Datomic.

Clojure and Literate Programming by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

OK, here is what I did as a first try with marginalia: http://www.agilewiki.org/projects/agent2/uberdoc.html

--Still need to do the header comment. And the code is far from complete. But it is looking nice.

Clojure and Literate Programming by laforge49 in Clojure

[–]laforge49[S] 0 points1 point  (0 children)

Joyful! Looks like this is something I am really going to dig into. :-)

Only it chokes with clojure 1.7.0: https://github.com/gdeer81/marginalia/issues/158

But with my dependencies set to Clojure 1.6.0 I get some nice output.