Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ports Drilling #812

Open
emil14 opened this issue Dec 18, 2024 · 2 comments
Open

Ports Drilling #812

emil14 opened this issue Dec 18, 2024 · 2 comments
Assignees
Labels

Comments

@emil14
Copy link
Collaborator

emil14 commented Dec 18, 2024

The Problem

Classical "props drilling" problem existing in e.g. React and Go. Abstractions to solve this in both platforms called "context".

In Go, unlike React, Context also solves #813 but we are want to only talk about first problem - sharing data between nodes in a component-tree without explicit message passing.

Why Not Global API?

Also note that there is #811 that kinda solves this problem but not specifically for parent-child hierarchies, which is critical for solving ports drilling problem. Global scope allows implicit data sharing but doesn't have required constraints, e.g. it allows siblings to share data, or it allows parent to read data that child saved.

In the context API a node can only get the data that was saved at parent level and save the data at its own level for its children. Unlike Global Scope API it's uni-directional dataflow.

In React code and data is the same thing, so parent component can insert a closure into context, this way child can technically send data to the parent. This is however still uni-directional dataflow. In Nevalang on the other hand data are messages and code are components. It's impossible to insert Nevalang component into a message, so Nevalang Context API must be limited in that regard. It's very important to be uni-directional, unlike Global Scope API. Context is a feature that used a lot.

@emil14 emil14 self-assigned this Dec 18, 2024
This was referenced Dec 18, 2024
@emil14
Copy link
Collaborator Author

emil14 commented Dec 18, 2024

Context API (unsafe)

Solving ports drilling problem

import {
    fmt
    context
    runtime
}

def Main(start any) (stop any) {
    ctx_set context.Set
    sub SubNode
    ---
    :start -> 42 -> ctx_set:v
    'foo' -> ctx_set:k
    ctx_set -> sub -> :stop
}

def SubNode(sig any) (res any) {
    sub_sub SubSubNode
    ---
    :sig -> sub_sub -> :res
}

def SubSubNode(sig any) (res any) {
    ctx_get context.Get
    println fmt.Println
    panic runtime.Panic
    ---
    :sig -> 'foo' -> ctx_get
    ctx_get:some -> println -> :res
    ctx_get:none -> panic
}

Should print 42


On the other hand it must not work vice versa. If SubSubNode will set something on context it will only affect context at that level. Those changes won't be seen by SubNode and Main.

If SubNode will set something on context it will affect both SubSubNode and SubNode, but not Main.

If Main will set something on context it will affect all SubNode,SubSubNode and SubSubNode.


However this API is not type-safe because context this way can only send any type.

Go solves this with type-assertions but we can't do that, our type-system is structured and messages do not carry any type-related metadata at runtime.

@emil14
Copy link
Collaborator Author

emil14 commented Dec 21, 2024

def Foo(data int) (res int) {
    get_user_id context.Get<int>{MustUserIDFromCtx}
    db users.Set
    ---
    :data -> [get_user_id, db:amount]
    get_user_id -> db:user_id
    db -> :res
}

def MustUserIDFromCtx(ctx dict<any>) (res int) {
    panic runtime.Panic
    ---
    ctx["userID"] -> match {
        Some -> :res
        None -> "userID not found in ctx" -> panic
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant