Jump to page: 1 2
Thread overview
September 28

Hi,

Ever had a bit of feature-envy about Odin's "context" feature [1]? It is something used to pass "contextual" parameters, like a logger, an allocator, to callees. It is akin to Scala's "implicit parameters", or Jai contexts [2].

So I went ahead and implement a proof-of-concept library to have scope globals in D with a TLS-based stack of environments. I went ahead and implement a minimal logger, allocator, and "user pointer" API on top of that context system.

It has a worse API and usability than a language solution but basically I think there is no big blocker, if TLS and C runtime are available to you.

DUB: https://code.dlang.org/packages/implicit-context

implicit-context is currently limited and suggestions/requests/forks/destroying are much welcome.

As I see it, without compiler support the pluses are:

  • no hidden "context" parameter in function calls, no new ABI
  • contextual parameters are a bit like "scoped globals", they will not change that often.

and the minuses are:

  • manual push/pop
  • wordy, opDispatch getter doesn't seem possible?

[1] https://odin-lang.org/docs/overview/#implicit-context-system
[2] https://medium.com/@christoffer_99666/a-little-context-d06dfdec79a3

September 29

On Thursday, 28 September 2023 at 23:28:02 UTC, Guillaume Piolat wrote:

>

Hi,

Ever had a bit of feature-envy about Odin's "context" feature [1]? It is something used to pass "contextual" parameters, like a logger, an allocator, to callees. It is akin to Scala's "implicit parameters", or Jai contexts [2].

[...]

Interesting, what are the benefits of using this instead of global variables?

September 29

On Friday, 29 September 2023 at 08:33:56 UTC, Imperatorn wrote:

>

Interesting, what are the benefits of using this instead of global variables?

Thinking about this, it's more vs TLS variable. __gshared would require synchronization.

Changing the theAllocator (a TLS variable) in std.experimental.allocator looks like this:

    auto save = theAllocator;
    theAllocator = myAllocator;

    // do stuff with custom allocator

    theAllocator = save;

Changing the allocator in implicit-context looks like this

    context.push;
    context.allocator = myAlloc;

    // do stuff with custom allocator

    context.pop;

so now that I think about it I'm not sure if there is an substantial advantage over simply having TLS variables. I had the goal of allowing .alloca on that secondary stack. If there is many context variables, push and pop will be a bit faster to write than all the temporaries.

September 29

On Friday, 29 September 2023 at 11:00:05 UTC, Guillaume Piolat wrote:

>

On Friday, 29 September 2023 at 08:33:56 UTC, Imperatorn wrote:

>

Interesting, what are the benefits of using this instead of global variables?

Thinking about this, it's more vs TLS variable. __gshared would require synchronization.

Changing the theAllocator (a TLS variable) in std.experimental.allocator looks like this:

    auto save = theAllocator;
    theAllocator = myAllocator;

    // do stuff with custom allocator

    theAllocator = save;

Changing the allocator in implicit-context looks like this

    context.push;
    context.allocator = myAlloc;

    // do stuff with custom allocator

    context.pop;

so now that I think about it I'm not sure if there is an substantial advantage over simply having TLS variables. I had the goal of allowing .alloca on that secondary stack. If there is many context variables, push and pop will be a bit faster to write than all the temporaries.

I understand, it's more like if you mix optional parameters and dependency injection?

I think for this to be truly valuable, it would require being part of the language.

I admit I haven't really thought about implicit parameters before your post, so I might be missing something.

September 29

On Friday, 29 September 2023 at 15:00:33 UTC, Imperatorn wrote:

>

I think for this to be truly valuable, it would require being part of the language.

Only if proven on DUB.

>

I admit I haven't really thought about implicit parameters before your post, so I might be missing something.

Think of it like envvars for threads. When you launch a process, the launcher knows to copy the environment variables. With scattered TLS variables, no new thread can get a copy of all the "context" it may have. But with a centralized place for context, you will be able to do that (not implemented yet), which kinda improves encapsulation.

Example 1: Context variables are scoped

In a UI library, every new widget typically get a "UI context" (that, or factory functions only) polluting all the constructors there is.

Solution: Just append "uiContext" variable in the context => less parameters.

But it could be a TLS variable, right? Yes indeed, but then you may want to scope that, to remove the global from being accessed outside a particular scope. Globals can be accessed at any time, which doesn't improve a public API.

Example 2

When my "gfm" package was forked to "gfm7", the first thing that was done is:

  • remove all the _gl members that would only there to confirm a particular shared library was used => it is part of the context
  • and removed all logger interface passing => also ugly and kinda never changes
September 29
On 29.09.2023 18:30, Guillaume Piolat wrote:
> On Friday, 29 September 2023 at 15:00:33 UTC, Imperatorn wrote:
>>
>> I think for this to be truly valuable, it would require being part of the language.
> 
> Only if proven on DUB.
> 
>> I admit I haven't really thought about implicit parameters before your post, so I might be missing something.
> 
> Think of it like envvars for threads. When you launch a process, the launcher knows to copy the environment variables. With scattered TLS variables, no new thread can get a copy of all the "context" it may have. But with a centralized place for context, you will be able to do that (not implemented yet), which kinda improves encapsulation.
> 
> 
> **Example 1: Context variables are scoped**
> 
>    In a UI library, every new widget typically get a "UI context" (that, or factory functions only) polluting all the constructors there is.
> 
> Solution: Just append "uiContext" variable in the context => less parameters.
> 
> But it could be a TLS variable, right? Yes indeed, but then you may want to scope that, to remove the global from being accessed outside a particular scope. Globals can be accessed at any time, which doesn't improve a public API.
> 
> 
> **Example 2**
> 
> When my "gfm" package was forked to "gfm7", the first thing that was done is:
> - remove all the _gl members that would only there to confirm a particular shared library was used => it is part of the context
> - and removed all logger interface passing => also ugly and kinda never changes
> 
> 

But later I understand that the removing of gl has also disadvantages. In general implicit context is really the great idea
September 29

On Friday, 29 September 2023 at 15:30:30 UTC, Guillaume Piolat wrote:

>

On Friday, 29 September 2023 at 15:00:33 UTC, Imperatorn wrote:

>

[...]

Only if proven on DUB.

>

[...]

Think of it like envvars for threads. When you launch a process, the launcher knows to copy the environment variables. With scattered TLS variables, no new thread can get a copy of all the "context" it may have. But with a centralized place for context, you will be able to do that (not implemented yet), which kinda improves encapsulation.

[...]

Sounds a bit like dependency injection but for state

September 30

On Friday, 29 September 2023 at 16:56:47 UTC, Imperatorn wrote:

>

Sounds a bit like dependency injection but for state

Possibly, I'm not familiar with dependency injection.
When is it useful?

September 30

On Saturday, 30 September 2023 at 12:40:29 UTC, Guillaume Piolat wrote:

>

On Friday, 29 September 2023 at 16:56:47 UTC, Imperatorn wrote:

>

Sounds a bit like dependency injection but for state

Possibly, I'm not familiar with dependency injection.
When is it useful?

Dependency injection is a principle of making your classes/functions self-contained and isolated, it means that when your code might need to create a resource (such as open a file to write data) it is instead up to the caller to provide that resource, but your code never does that by itself because a library can't possibly know the environment and restrictions of the target system/machine.

Simply put, you can't possibly know how to open a file in that system, you can't possibly know what allocator is used in the caller environment (think about very low-level or bare metal program), and so on, so instead caller must provide everything that your function/method/class might need to do the work.

In the most complex situations where the entire program graph is about to be created in the main function there is so called DI containers that configures all this stuff in one central place.

September 30

On Saturday, 30 September 2023 at 12:40:29 UTC, Guillaume Piolat wrote:

>

On Friday, 29 September 2023 at 16:56:47 UTC, Imperatorn wrote:

>

Sounds a bit like dependency injection but for state

Possibly, I'm not familiar with dependency injection.
When is it useful?

When you want to register a bunch of objects and then just use it from various places, you just state in your ctor that you want to use it and it will be provided by the framework.

« First   ‹ Prev
1 2