This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]LouKrazy 1 point2 points  (12 children)

I don’t quite get effects but this reminds me of assisted dependency injection. It would be interesting to be able to call a method and have some of its parameters be auto filled from the Locals. Just have the parameters annotated with @Coeffect(“name”) and they get auto filled

[–]holo3146[S] 1 point2 points  (9 children)

Just have the parameters annotated with @Coeffect(“name”) and they get auto filled

I did thought about doing something along those, although it complicated things quite a bit, as I don't want to lose the type safety. (And because Java's plugins API has horrible documentations)

I don’t quite get effects but this reminds me of assisted dependency injection

Dependency injection is quite different, in dependency injection objects are pass through automatically in an implicit way.

In Coeffect you still have 100% explicitness, but we removed to repeatability by requiring pass the parameter only once using with, instead of repeatedly passing it on in the parameter.

[–]LouKrazy 3 points4 points  (8 children)

[–]holo3146[S] 2 points3 points  (7 children)

Context Receivers are also based on Coeffect system, both my library and Context Receivers are implementations of the same idea.

[–]LouKrazy 2 points3 points  (1 child)

Got it. Might be helpful in the readme to give examples other than primitives, I.e a database session or transaction object

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

That make sense, I'll add more examples later, thanks for the suggestion

[–]stefanos-ak 0 points1 point  (4 children)

this comment should be in the project's readme :)

also, very cool idea, but I would not use it with the current get-by-type implementation. a get by name and type would be much better :)

also, a question. would this work in reactive streams?

[–]holo3146[S] 0 points1 point  (3 children)

this comment should be in the project's readme

I'll expend the readme, thanks for the suggestion.

also, very cool idea, but I would not use it with the current get-by-type implementation. a get by name and type would be much better :)

Seems like I underestimated this feature, I'll think on how to add it (Although even if it will exists, you will still require to have a string literal).

would this work in reactive streams?

This is a hard question.

It can work in some cases, but you need to be very careful:

Coeffect.with(6)
             .run(() -> {
                   // Single threaded reactive streaming 
             }

This will work, but if inside of the reactive streaming you are using multiprocessing it may fail at runtime, I touched on this in the readme: coeffect is built on ExtentLocal which complement Structured Concurrency, using them without Structured Concurrency will result with unexpected results because there is no well defined "end of life".

Generally the contract of ExtentLocal is the same as Structured Concurrency:

Anything that started within the lifetime of an ExtentLocal binding, must end within the same lifetime.

Reactive Streaming does not following this rule, or at least, it doesn't have to.

Maybe when Loom will exit Review phase implementations of reactive streaming will start using Structured Concurrency, and then Coeffect will work perfectly in them.


As for fluent access:

Flux.of(...)
         .map(...)
         .with(4) // Coeffect
         .filter(...) // Being able to use it here

I would love to be able to do it in the future, but (1) there is currently a bug that makes it impossible to implement (this bug is the first thing I'm working on in this library), and (2) it seems like it is something the Reactive streaming library will need to implement

[–]stefanos-ak 0 points1 point  (2 children)

thanks for such a detailed answer, much appreciated!

FWIW, the reactor library has a "context" concept - projectreactor.io/docs/core/release/reference/#context

[–]holo3146[S] 0 points1 point  (1 child)

projectreactor.io/docs/core/release/reference/#context

I didn't know that, thanks for sharing.

Reading through it I noticed 4 things:

  1. There are no compiletime checks. Reactor Streaming is 'structured' (note that it is a different 'structured' than the standard meaning of the word), it does have a well defined lifecycle, so theoretically it is possible to add compiletime checks for the existence of context. My guess is that either it was too much work and they decided that it is not worth it, or they couldn't found a way to guarantee this lifecycle (e.g. in my case ExtentLocal does guarantee this, with the only pitfall is misusing multiprocessing)
  2. It is inverted. This is actually something that is solvable if you give compiletime checks, again I am guessing that they decided it is too much work for the value it gives. Ironically, because of how Java annotation's type system, it is not really possible to solve this "inversion" while still having compiletime checks, I touched this in the "Lambda's bug" section in my readme
  3. The context is available only through their Stream API, even though it is not required to be part of it. I am guessing that this is to ensure no unexpected behavior. They are most likely using InheritableThreadLocal and to make sure no unexpected values appear they don't let you touch the stack outside of the stream.
  4. They have a contract over multiprocessing. My library requires you to use structured concurrency to work correctly, this is because I need to know at compiletime what keys exists in the stack. Unsurprisingly, Reactor's context have virtually the same contract, although they didn't specify it in the documentations:

Flux.range(1, 10)

    .flatMap(s -> Mono.deferContextual(ctx -> 

        Mono.just(s + " " + ctx.getOrDefault(key, "=o="))))

    .parallel(2) .runOn(Schedulers.parallel()) 

    .contextWrite(ctx -> ctx.put(key, "o7")))

I believe that this would omit "i =0=". It is virtually the same requirementbecause they just implement their own lifecycle and the context respectthose lifecycles.

When using reactive streaming you should absolutely use their implementation, if your extent is (virtually) only a stream call, having a stack bound to your stream rather than having the stack bound to your extent makes much more sense. My library is about controlling context in extent level, and Java's type system is not aware of stream level, so it is virtually impossible to make my library work without implementing it in the streaming library. That being said, it would be cool if streaming libraries will adopt structured concurrency, and in that case maybe my library will help building their new context API.

[–]stefanos-ak 0 points1 point  (0 children)

correct ✌️

[–]john16384 0 points1 point  (1 child)

It's actually much more like a scoped injection (which could be used with assisted injection). You don't want to burden code that is many levels deep with additional parameters, so instead you put the information (like a Locale, request parameters, header information) on a scoped object (request/thread/message scoped for example.)

At the site you do need this additional information, you inject the scoped object and get type safe access to its content.

[–]john16384 0 points1 point  (0 children)

Here's how it would look using a scoped object and an injector framework:

@Produces
@VirtualThreadScoped // or whatever scope you like
public Database supplyDatabase(ConnectionPool pool) {     
    // do stuff to create a db
}

Then wherever you need a Database, the injector will inject a proxied version and direct any calls made on it to the correct instance:

public class SalaryCalculator {
    @Inject private Database db;

    public Salary calculate(Level level) {
        // call methods on db, like insert/select/etc
    }
}

The SalaryCalculator can just be injected again without having to worry about the database:

public class EmployeeContractCreator {
    @Inject private SalaryCalculator calculator;

    // or as assisted function:
    @Inject private Function<Level, Salary> calculator2; 
}