Direct style for concurrent systems

That’s actually an interesting point to discuss.

I assume that the information you are seeking is that the result of a function will not be immediately available (i.e. will block or take time to produce). But there is plenty of pure computations that can behave that way, for example calling:

let f () = for i = 0 to max_int / 100 do Sys.opaque_identity () done

will block you for quite some time. Yet this will never show up in the type. But how is that different from waiting on, say, a server response on your fd ?

Not only you can’t really rely on these “informative signatures”, they also become cruft if you are perfectly fine with blocking/waiting on the result. In this case I shouldn’t be bothered with async/await non-sense. Only concrete concurrent/parallel activity orchestration should be mirrored in the type. It’s a separation of concerns (very much like when I write codecs I couldn’t care less from where and how the bytes are flowing from/to, hence bytesrw).

2 Likes

Maybe I am misunderstanding your objection, but I don’t look at the type information as saying:

But rather, that evaluation “can progress concurrently” with other values of this type. (Equivalently, can progress parallel-ly for threads / domains if there were such a monad).

That interpretation does not seem so incongruous with your other comment:

1 Like

That view doesn’t change much. You can also do something else while f () is running.

But there is plenty of pure computations that can behave that way

And I would welcome a type system that gives information about functions where calls can take a significant amount of time to compute!

In fact, something quite difficult to manage when coding with Lwt (and I’d suspect the same happens for Async) is that calling a CPU-intensive function causes stalls for all IO-bound tasks. It is recommended to wrap those calls in Lwt_preemptive.detach or Lwt_domain.detach. It’d be very nice to have type-level information indicating the need for those.

Yes, I do know how much harder it is to provide this “CPU intensive” information than wrapping all IO in a type and progpagating this type information. But nonetheless, if your argument against having “this function might be arbitrarily delayed by IO” is “well there’s no type information about computations being arbitrarily dealyed by CPU” then it’s not necessarily an argument against having IO-blocking information.
Some information is (often) better than none.

1 Like

Be careful with what you wish :–) Precise types are not necessarily nice and ergonomic to work with. As was once said by B. Pierce (IIRC), the ML type system is a sweet spot.

But that’s not factoring in the costs. The cost here results in a mecanism that composes extremely poorly (a monad) and that you still need to be quite careful about (as witnessed by Lwt_preemptive).

You can try to add more types to solve that (even though I doubt a bit, the notion of significant amount of time is contextual, operating systems solved that by simply introducing preemptive scheduling) but this starts to look like an escalation of commitment to what is perhaps not a good idea in the first place, namely encoding blocking/available later information in the return types of primitive operations as promise/future/lwt/async/potato values.

Again, separation of concerns. There are tons of scenarios where it brings absolutely nothing except bureaucracy/non-compositionality for my logic to know that other things may be happening in parallel. And yet the system forces me to think about it by imposing me to work trapped in a future value.

Even in a simple CRUD webs server, most of the time, your request/response cycles do not need to know that there are other parallel requests that are being made by other users. Why does the heck have the logic of my request have to deal with a future value if all I’m doing is a code that simply queries the db and writes the result as a response ? It’s the duty of the server code (and db library) to schedule my computations in parallel, the request/response code does not need to know about it.

2 Likes

Ok that’s a different argument and I understand that you don’t want to have to add any form of logic to your code when it’s not your code’s responsibility to deal with it.

It’s the duty of the server code (and db library) to schedule my computations in parallel, the request/response code does not need to know about it.

Ideally yes. In practice, you might be interacting with a db as well as some other parts of the system (calling some webhooks, creating new directories and files, etc.). Having the information available at the type level that the db access might incurr a delay during which other code might execute can help you take care of these other interactions.

There’s a cost/benefit thing going on. And yes, the costs can be quite high. (Although in the case of Lwt, I’d argue the main cost is not so much the * you need to add to your let, but more so the lack of stack traces.)

To me it’s kind of evident that a database call will block (and as already mentioned you won’t have that information at the type level for other kind of “blocking” operations like long running computations).

So if I really need to perform something in parallel to the database call I’ll just spawn a parallel function call in my request, at which point I will then have to handle and pay for coordination costs at the value level. That is only when I actually need it.

In tact I’m not so much against having information about the fact that a function call may block with for example a different kind of arrow type, than having that information polluting the result type of my functions. Seing parallel activity in my result type should only happen if my function explicitely issues a parallel activity that it leaves unsynchronized.

Until you want to use something else that adds a * on your lets. Trying to handle that with monad transformers to coordinate all that feels again like escalation of commitment, you end up fighting with an unergonomic type structure rather than looking at the evidence: monads compose poorly.

3 Likes

100% agree, it’d be more ergonomic

looking forward to the effect typing possibly offering some way to do this

1 Like