candid icon indicating copy to clipboard operation
candid copied to clipboard

JavaScript Ergonomics

Open krpeacock opened this issue 4 years ago • 5 comments

Currently, there are two issues with the JavaScript / TypeScript bindings that make development inconvenient for IC developers.

  1. Options
  • Optional nested values in JavaScript are denoted idiomatically using the Optional Chaining operator (?.)
  • Our generated pattern, of a null value being represented as [] does not align with expectations of developers, as Arrays are used to represent ordered data.
  1. Variants
  • For Variants such as Result.Result, we get back a typing that is not ideal for JavaScript, and requires developers to add a significant amount of boilerplate and error handling.
  • a type of { ok : () } | { err: string } cannot be safely accessed as result.ok or result.err, but requires the user to write something like
if ('ok' in result){
 // continue
}
else if ('err' in result) {
 // handle result.err
}
* This code is unsafe, and is problematic for TypeScript in particular. Instanceof is also not reliable for determining which case is represented.

Describe the solution you'd like

  • Replace options with Optional Chaining
    • may require additional support in the JS Agent for converting null back to the candid type
  • Handle variants with a "kind" attribute that will always be present and checkable in variants

krpeacock avatar Dec 09 '21 19:12 krpeacock

Optional nested values in JavaScript are denoted idiomatically using the Optional Chaining operator (?.)

I don't quite follow. The problem with nested opt is which values to use to represent them; ?. is an operator on values.

Which values would you use for null, opt null and opt 5 at type opt opt int?

nomeata avatar Dec 09 '21 21:12 nomeata

I can see that the ability to use a switch over a variant would be desirable. But can you elaborate why this code is "unsafe and problematic" in TypeScript?

How would you suggest to represent argumentless variants like variant {a; b}, or more interestingly, mixed ones like variant {a; b : nat}? TypeScript would idiomatically represent the former as "a" | "b", but that representation would not allow using a single switch for the latter.

rossberg avatar Dec 10 '21 08:12 rossberg

Which values would you use for null, opt null and opt 5 at type opt opt int?

null would be null. opt null would be undefined | null. opt 5 would be undefined | number | bigint, and I think I would flatten nested opts to simply undefined | <T>. We can still nest them appropriately as arrays over the wire, but at the end of the day there either is a value at the end of the chain, or there isn't. Unless I am failing to imagine a case here

krpeacock avatar Feb 04 '22 17:02 krpeacock

I can see that the ability to use a switch over a variant would be desirable. But can you elaborate why this code is "unsafe and problematic" in TypeScript?

instanceof interacts poorly with JavaScript imports, and a compatible interface from a different package will not always yield consistent results. Different versions of @dfinity/agent and @dfinity/principal can end up with instanceof Principal not behaving as expected. This is why we provide methods like Principal.prototype.isPrincipal(). I wouldn't forbid someone from using it, but relying on instanceof for non-native types is a pattern that will tend to introduce bugs for devs.

How would you suggest to represent argumentless variants like variant {a; b}, or more interestingly, mixed ones like variant {a; b : nat}? TypeScript would idiomatically represent the former as "a" | "b", but that representation would not allow using a single switch for the latter.

I would represent argument-less variants as strings of the variant name. This handles mixed variants well enough, and allows for strong type inspection into example.kind

type exampleType = { kind: "a" } | { kind: {b: bigint} }

image

krpeacock avatar Feb 04 '22 17:02 krpeacock

After discussing with @chenyan-dfinity , we agreed that nested opts should be typed the same as they are now, with nested empty arrays

krpeacock avatar Feb 15 '22 03:02 krpeacock