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

Improvements to Transaction Usage Syntax #51

Closed
CMCDragonkai opened this issue Aug 6, 2022 · 2 comments
Closed

Improvements to Transaction Usage Syntax #51

CMCDragonkai opened this issue Aug 6, 2022 · 2 comments
Assignees
Labels
enhancement New feature or request r&d:polykey:supporting activity Supporting core activity

Comments

@CMCDragonkai
Copy link
Member

Is your feature request related to a problem? Please describe.

The usage of transactions could benefit from syntax helpers.

Right now without having a monadic expression, methods have to thread the DBTransaction.

class X {
  public async f(tran?: DBTransaction) {
    if (tran == null) {
      return this.db.withTransactionF(
        (tran) => this.f.apply(this, [...arguments, tran])
      );
    }
    // use tran
  }
}

Describe the solution you'd like

Instead something like this could be possible:

class X {
  @transaction(this.db)
  public async g(tran: DBTransaction) {
    // use tran
  }
}

This is currently not possible until decorators are able to change the method signatures. That is, currently TS decorators are not allowed to change method signatures so then tran here is actually required by the caller.

At the same time, the TypedDescriptor should be used to allow a union of different methods. Any method that takes the tran: DBTransaction at the very end of the function is allowed. The usage of withTransactionF and withTransactionG is the only ones allowed, as one must do it inside an async function or an async generator function.

At the same time, this syntax enhancement only works for functions with a very specific signature.

Describe alternatives you've considered

The main alternative is a monadic API. Monads have a bind function where M a -> (a -> M b) -> M b.

Here the Transaction is the monadic context, that functions are bound to.

If we were in Haskell we could do:

tran >>= (\s -> return s) >> put "key" "value" >> get "key"

What is the monad actually wrapping? It's not the database... I suppose it is the state. Then the state can be interrogated there. But the get and put would be transactional methods. It could really wrap anything.

To allow do syntax to be possible, one must make our ; the equivalent of the bind operator.

class X {
  public async f(): DBTransaction<void> {
    put();
    get();
    return;
  }
}

Then put and get represent transactional operators, that can only composed with an existing monad.

This isn't idiomatic JS atm, so it's not really possible.

The other way is to use this and then use class decorator mixins that enable the context of the function to be augmented with operators that make it transactional. But that adds even more magic.

Once method signatures can be changed, then it would be worthwhile for application placed decorators to do this. It could also work if instead of parameters, we had keyword/named parameters. Which would make a @transaction decorator alot more robust.

Another way is to allow users to define their decorator functions easily, so that way they can address different kind of function signatures. Like have the transaction be the first parameter, last parameter or anywhere in between.

Without method signature changes, you would need to use tran! inside the function to ensure that TS understands that's it is in fact always defined.

Additional context

function transaction(db: { a: string }) {
  return (
    target: any,
    key: string,
    descriptor: TypedPropertyDescriptor<(tran: string) => any>
  ) => {
    const f = descriptor.value;
    if (typeof f !== 'function') {
      throw new TypeError(`${key} is not a function`);
    }
    descriptor.value = function (tran: string) {
      return this.db.withTransactionF(
        (tran) => f.apply(this, [...arguments, tran])
      );
    };
    return descriptor;
  };
}
@CMCDragonkai CMCDragonkai added the enhancement New feature or request label Aug 6, 2022
@CMCDragonkai
Copy link
Member Author

We are making this work inside PK. We have a way of overloading the decorator signatures, and with parameter decorators, we can specify where the context should be located. However since our context interface also contains other properties like signal and timer, right now it's still a thing inside PK, and cannot be factored out to independent library. Could be useful if we had an open interface for the context and allowed one to share the context map. MatrixAI/Polykey#297

@CMCDragonkai
Copy link
Member Author

Going to close this for now as the context decorators will be implemented in MatrixAI/Polykey#297.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request r&d:polykey:supporting activity Supporting core activity
Development

No branches or pull requests

1 participant