diff --git a/docs/catalyst-reports.md b/docs/catalyst-reports.md index 2027a5bc..662aba23 100644 --- a/docs/catalyst-reports.md +++ b/docs/catalyst-reports.md @@ -10,3 +10,4 @@ finished Catalyst projects here: - [Milestone 4: Project adoption](catalyst09-reports/milestone-3.md) - [Catalyst 10 reports](catalyst10-reports) - [Milestone 1: Rust support](catalyst10-reports/milestone-1.md) + - [Milestone 2: Javascript/Typescript support](catalyst10-reports/milestone-2.md) diff --git a/docs/catalyst10-reports/milestone-1.md b/docs/catalyst10-reports/milestone-1.md index ae9ba6d0..8a5c3632 100644 --- a/docs/catalyst10-reports/milestone-1.md +++ b/docs/catalyst10-reports/milestone-1.md @@ -27,7 +27,7 @@ - [x] LambdaBuffers schemas that are based on the LambdaBuffers Prelude module can be used in Rust projects to specify application types. - Test libraries for Prelude demonstrate how Rust code for LambdaBuffers Prelude is generated to Rust and used in a library: [testsuites/lbt-prelude/lbt-prelude-rust](https://github.com/mlabs-haskell/lambda-buffers/tree/c83cdf9b881a95b8607821027c3551ecd56c9447/testsuites/lbt-prelude/lbt-prelude-rust) - [x] LambdaBuffers schemas that are based on the LambdaBuffers Plutus module can be used in Rust projects to specify application types. - - Test libraries for Prelude demonstrate how Rust code for LambdaBuffers Plutus is generated to Rust and used in a library: [testsuites/lbt-plutus/lbt-plutus-rust](https://github.com/mlabs-haskell/lambda-buffers/tree/c83cdf9b881a95b8607821027c3551ecd56c9447/testsuites/lbt-plutus/lbt-plutus-rust) + - Test libraries for Plutus demonstrate how Rust code for LambdaBuffers Plutus is generated to Rust and used in a library: [testsuites/lbt-plutus/lbt-plutus-rust](https://github.com/mlabs-haskell/lambda-buffers/tree/c83cdf9b881a95b8607821027c3551ecd56c9447/testsuites/lbt-plutus/lbt-plutus-rust) - [x] The documentation and devops tooling is available to facilitate easy adoption. - Similarly to other languages supported by LambdaBuffers, a Rust flake is implemented. The testing libraries also serve as an example, to understand how to use these Nix utilities: [testsuites/lbt-plutus/lbt-plutus-rust/build.nix](https://github.com/mlabs-haskell/lambda-buffers/tree/c83cdf9b881a95b8607821027c3551ecd56c9447/testsuites/lbt-plutus/lbt-plutus-rust/build.nix) diff --git a/docs/catalyst10-reports/milestone-2.md b/docs/catalyst10-reports/milestone-2.md index 844f7301..05693803 100644 --- a/docs/catalyst10-reports/milestone-2.md +++ b/docs/catalyst10-reports/milestone-2.md @@ -1,38 +1,72 @@ -# Catalyst milestone 2: Javascript support +# Catalyst milestone 2: Javascript/Typescript support + +While the milestone technically requires Javascript support, we implemented [Typescript](https://www.typescriptlang.org/) support which is a [typed superset of Javascript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html#a-typed-superset-of-javascript). This was done to better interpolate with existing Javascript/Typescript libraries on Cardano such as [lucid](https://github.com/spacebudz/lucid), [cardano-js-sdk](https://github.com/input-output-hk/cardano-js-sdk), [cardano-serialization-lib](https://www.npmjs.com/package/@emurgo/cardano-serialization-lib-nodejs), etc. With Typescript support, we get Javascript support for free as the compilation from Typescript to Javascript is a well established practise. ## Outputs -- [ ] A LambdaBuffers code generation module that outputs type constructors and derived implementations in the Javascript programming language given a LambdaBuffers schema. -- [ ] A Javascript library that implements the LambdaBuffers Prelude runtime. This module would include standardised JSON encoding and equality implementations for all declared type class instances in the schema. -- [ ] A Javascript test suite that assures the manually implemented and automatically generated implementations are consistent with the predefined LambdaBuffers Prelude golden data set of JSON files and perform correct implementation derivation. -- [ ] A Javascript library that implements the LambdaBuffers Plutus runtime. This module would include standardised PlutusData encoding implementations for all declared type class instances in the Plutus schema. -- [ ] A Javascript test suite that assures the manually implemented and automatically generated implementations are consistent with the predefined LambdaBuffers Plutus golden data set of PlutusData encoded files and perform correct implementation derivation. -- [ ] Nix devops modules (Nix API) for streamlining the LambdaBuffers code generation pipeline to Javascript. -- [ ] Documentation on LambdaBuffers usage patterns for Javascript. +- [x] A LambdaBuffers code generation module that outputs type constructors and derived implementations in the Javascript programming language given a LambdaBuffers schema. + - The module implementation is in [lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/2e2ed98f4df7f207ec3cf131adf5b47b202e248f/lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript). -## Acceptance Criteria +- [x] A Javascript library that implements the LambdaBuffers Prelude runtime. + This module would include standardised JSON encoding and equality + implementations for all declared type class instances in the schema. + - A standalone Prelude library for Typescript was implemented together with + its runtime for LambdaBuffers. These can be found here: + - [prelude-typescript](https://github.com/mlabs-haskell/prelude-typescript) + - [runtimes/typescript/lbr-prelude](https://github.com/mlabs-haskell/lambda-buffers/tree/343a1a900f42dcf5b1c1a7e330eafb07c280908b/runtimes/typescript/lbr-prelude) -- [ ] LambdaBuffers schemas that are based on the LambdaBuffers Prelude module can be used in Javascript projects to specify application types. -- [ ] LambdaBuffers schemas that are based on the LambdaBuffers Plutus module can be used in Javascript projects to specify application types. -- [ ] The documentation and devops tooling is available to facilitate easy adoption. +- [x] A Javascript test suite that assures the manually implemented and automatically generated implementations are consistent with the predefined LambdaBuffers Prelude golden data set of JSON files and perform correct implementation derivation. + - A test suite ensuring that the manually implemented and automatically generated implementations can be found here: [testsuites/lbt-prelude/lbt-prelude-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-prelude/lbt-prelude-typescript). +- [x] A Javascript library that implements the LambdaBuffers Plutus runtime. This module would include standardised PlutusData encoding implementations for all declared type class instances in the Plutus schema. + - A standalone Plutus library for Typescript was implemented together with + its runtime for LambdaBuffers. These can be found here: -## Evidence of Milestone Completion + - [plutus-ledger-api-typescript](https://github.com/mlabs-haskell/plutus-ledger-api-typescript) -- [ ] The completed and reviewed LambdaBuffers Prelude runtime library is available for the Javascript programming language. -- [ ] The completed and reviewed LambdaBuffers Plutus runtime library is available for the Javascript programming language. -- [ ] The completed and reviewed LambdaBuffers Prelude test suite is available and is passing in CI for the Javascript programming language. -- [ ] The completed and reviewed LambdaBuffers Plutus test suite is available and is passing in CI for the Javascript programming language. -- [ ] The completed and reviewed Nix API for LambdaBuffers Javascript support is available. -- [ ] The completed and reviewed LambdaBuffers for Javascript documentation is available. + - [runtimes/typescript/lbr-plutus](https://github.com/mlabs-haskell/lambda-buffers/tree/dc5ee6797d1230661d6bb3dfa658eddeadd7cb60/runtimes/typescript/lbr-plutus) -## Demo recordings +- [x] A Javascript test suite that assures the manually implemented and automatically generated implementations are consistent with the predefined LambdaBuffers Plutus golden data set of PlutusData encoded files and perform correct implementation derivation. + - A test suite ensuring that the manually implemented and automatically generated implementations can be found here: [testsuites/lbf-plutus/lbf-plutus-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-plutus/lbt-plutus-typescript) +- [x] Nix devops modules (Nix API) for streamlining the LambdaBuffers code generation pipeline to Javascript. + - New flake modules were implemented to easily generate NPM packages from LambdaBuffers + - [lbf-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-typescript.nix) + - [lbf-prelude-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-prelude-typescript.nix) + - [lbf-plutus-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-plutus-typescript.nix) +- [x] Documentation on LambdaBuffers usage patterns for Javascript. + - [A new page was created for the TypeScript use case](https://mlabs-haskell.github.io/lambda-buffers/typescript.html) -- [ ] +## Acceptance Criteria + +- [x] LambdaBuffers schemas that are based on the LambdaBuffers Prelude module can be used in Javascript projects to specify application types. + - Test libraries for Prelude demonstrate how TypeScript code for the LambdaBuffers Prelude is generated to TypeScript and used in a library: [testsuites/lbt-prelude/lbt-prelude-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-prelude/lbt-prelude-typescript) + - Moreover, there is a [docs/typescript-prelude](https://github.com/mlabs-haskell/lambda-buffers/tree/1d806a1710aab625ea520c596a72338c5bde578d/docs/typescript-prelude) sample project which also demonstrates this. +- [x] LambdaBuffers schemas that are based on the LambdaBuffers Plutus module can be used in Javascript projects to specify application types. + - Test libraries for Plutus demonstrate how TypeScript code for the LambdaBuffers Prelude is generated to TypeScript and used in a library: [testsuites/lbt-plutus/lbt-plutus-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-plutus/lbt-plutus-typescript) + - Moreover, there is a [docs/typescript-plutus](https://github.com/mlabs-haskell/lambda-buffers/tree/1d806a1710aab625ea520c596a72338c5bde578d/docs/typescript-plutus) sample project which also demonstrates this. +- [x] The documentation and devops tooling is available to facilitate easy adoption. + - Similarly to other languages supported by LambdaBuffers, a [TypeScript flake](https://github.com/mlabs-haskell/flake-lang.nix/tree/5bb4fdf556a2f2f23717c654c186f13f28b9c277/flake-lang/typescript) is implemented along with its documentation. + The testing libraries also serve as an example, to understand how to use these Nix utilities: + [testsuites/lbt-plutus/lbt-plutus-typescript/build.nix](https://github.com/mlabs-haskell/lambda-buffers/blob/4c6304cf3a3a0c08bbb46e94532a293fdea513e9/testsuites/lbt-plutus/lbt-plutus-typescript/build.nix). -Demo files: +## Evidence of Milestone Completion -- [ ] +- [x] The completed and reviewed LambdaBuffers Prelude runtime library is available for the Javascript programming language. + - [runtimes/typescript/lbr-prelude](https://github.com/mlabs-haskell/lambda-buffers/tree/343a1a900f42dcf5b1c1a7e330eafb07c280908b/runtimes/typescript/lbr-prelude) + - [prelude-typescript](https://github.com/mlabs-haskell/prelude-typescript) +- [x] The completed and reviewed LambdaBuffers Plutus runtime library is available for the Javascript programming language. + - [runtimes/typescript/lbr-plutus](https://github.com/mlabs-haskell/lambda-buffers/tree/dc5ee6797d1230661d6bb3dfa658eddeadd7cb60/runtimes/typescript/lbr-plutus) + - [plutus-ledger-api-typescript](https://github.com/mlabs-haskell/plutus-ledger-api-typescript) +- [x] The completed and reviewed LambdaBuffers Prelude test suite is available and is passing in CI for the Javascript programming language. + - [testsuites/lbt-prelude/lbt-prelude-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-prelude/lbt-prelude-typescript) +- [x] The completed and reviewed LambdaBuffers Plutus test suite is available and is passing in CI for the Javascript programming language. + - [testsuites/lbf-plutus/lbt-plutus-typescript](https://github.com/mlabs-haskell/lambda-buffers/tree/50bfbc4a182275d42be978b5a251530bab84f4aa/testsuites/lbt-plutus/lbt-plutus-typescript) +- [x] The completed and reviewed Nix API for LambdaBuffers Javascript support is available. + - [lbf-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-typescript.nix) + - [lbf-prelude-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-prelude-typescript.nix) + - [lbf-plutus-typescript](https://github.com/mlabs-haskell/lambda-buffers/blob/f59bdb78d06fa677567d053eddb3d1fe46250fd8/extras/lbf-nix/lbf-plutus-typescript.nix) +- [x] The completed and reviewed LambdaBuffers for Javascript documentation is available. + - [TypeScript documentation](google.com) TODO(jaredponn) put the commit here just before this when this is reviewed -## References +## Demo files -- [ ] +- Demo project: [lambda-buffers-for-cardano](https://github.com/mlabs-haskell/lambda-buffers-for-cardano/tree/main/transactions/demo-typescript) diff --git a/docs/typescript.md b/docs/typescript.md new file mode 100644 index 00000000..589116f7 --- /dev/null +++ b/docs/typescript.md @@ -0,0 +1,323 @@ +# LambdaBuffers to Typescript +This chapter will walk through a translation from a LambdaBuffers' module into a Typescript module. + +To demonstrate this, we will use the `lbf-prelude-to-typescript` CLI tool which is just a convenient wrapper over the raw `lbf` CLI. +To this end, we may enter a development shell which provides this tool along with many other Lambda Buffers CLI tools with the following command. + +```shell +$ nix develop github:mlabs-haskell/lambda-buffers#lb +$ lbf +lbf lbf-plutus-to-haskell lbf-plutus-to-rust lbf-prelude-to-haskell lbf-prelude-to-rust +lbf-list-modules-typescript lbf-plutus-to-purescript lbf-plutus-to-typescript lbf-prelude-to-purescript lbf-prelude-to-typescript + +``` + +Or, we may directly refer to the `lbf-prelude-to-typescript` CLI with the following command. + +```shell +nix run github:mlabs-haskell/lambda-buffers#lbf-prelude-to-typescript +``` + +In this chapter, we will use the former option. + +Consider the [Document.lbf](./examples/Document.lbf) schema which we may recall is as follows. + +```purescript +module Document + +-- Importing types +import Prelude (Text, List, Set, Bytes) + +-- Author +sum Author = Ivan | Jovan | Savo + +-- Reviewer +sum Reviewer = Bob | Alice + +-- Document +record Document a = { + author : Author, + reviewers : Set Reviewer, + content : Chapter a + } + +-- Chapter +record Chapter a = { + content : a, + subChapters : List (Chapter a) + } + +-- Some actual content +sum RichContent = Image Bytes | Gif Bytes | Text Text + +-- Rich document +prod RichDocument = (Document RichContent) +``` + +We generate the corresponding Typescript code with the following commands. + +```shell +$ nix develop github:mlabs-haskell/lambda-buffers#lb +$ lbf-list-modules-typescript lbf-document=. > lb-pkgs.json +$ lbf-prelude-to-typescript --gen-opt="--packages lb-pkgs.json" Document.lbf +$ find autogen/ +autogen/ +autogen/LambdaBuffers +autogen/LambdaBuffers/Document.mts +autogen/build.json +``` + +The generated `autogen` directory created contains the generated Typescript modules. + +Note that `lbf-list-modules-typescript` is needed to create a JSON object which maps package names (for NPM) to Lambda Buffers' modules. +Thus, in this example, one should have a `package.json` file which associates the key `"name"` with the string value `"lbf-document"`. + +The `autogen/build.json` file can be ignored. + +The file `autogen/LambdaBuffers/Document.mts` contains the outputted Typescript module: + +```ts +// @ts-nocheck +import * as LambdaBuffers$Document from './Document.mjs' +import * as LambdaBuffers$Prelude from './Prelude.mjs' + +export type Author = | { name : 'Ivan' } + | { name : 'Jovan' } + | { name : 'Savo' } +export const Author : unique symbol = Symbol('Author') +export type Chapter<$a> = { content : $a + , subChapters : LambdaBuffers$Prelude.List> + } +export const Chapter : unique symbol = Symbol('Chapter') +export type Document<$a> = { author : Author + , reviewers : LambdaBuffers$Prelude.Set + , content : Chapter<$a> + } +export const Document : unique symbol = Symbol('Document') +export type Reviewer = | { name : 'Bob' } | { name : 'Alice' } +export const Reviewer : unique symbol = Symbol('Reviewer') +export type RichContent = | { name : 'Image' + , fields : LambdaBuffers$Prelude.Bytes + } + | { name : 'Gif' + , fields : LambdaBuffers$Prelude.Bytes + } + | { name : 'Text' + , fields : LambdaBuffers$Prelude.Text + } +export const RichContent : unique symbol = Symbol('RichContent') +export type RichDocument = Document +export const RichDocument : unique symbol = Symbol('RichDocument') +``` + +## Product types +The type `RichDocument` have been declared as a product type in the LambdaBuffers schema using the `prod` keyword. + +In general, product types are mapped to [tuple types](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types) in Typescript most of the time. The exception is if there is only one element in the tuple in which case the type is translated to a type alias. + +More precisely, given a LambdaBuffers' product type as follows + +```purescript +prod MyProduct = SomeType1 ... SomeTypeN +``` + +where the `...` denotes iterated `SomeTypei` for some `i`, then + +- If `N = 0` so `prod MyProduct =`, then we map this to the Typescript type + + ```ts + export type MyProduct = [] + ``` + +- If `N = 1` so `prod MyProduct = SomeType1`, then we map this to the Typescript type + + ```ts + export type MyProduct = SomeType1 + ``` + + i.e., `MyProduct` simply aliases `SomeType1` + +- If `N >= 2` so `prod MyProduct = SomeType1 ... SomeTypeN`, then we map this to the Typescript type + + ```ts + export type MyProduct = [SomeType1, ..., SomeTypeN] + ``` + + i.e., `MyProduct` is a tuple with a fixed number of elements with known types. + +## Sum types +The types `Author`, `Reviewer`, and `RichContent` have been declared as sum types in the LambdaBuffers schema using the `sum` keyword. + +In general, sum types are mapped to a [union type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) in Typescript and with the additional following rules. +Given a LambdaBuffers' sum type as follows + +```purescript +sum MySum + = Branch1 Branch1Type1 ... Branch1TypeM1 + | ... + | BranchN BranchNType1 ... BranchNTypeMN +``` + +where the `...` denotes either an iterated `Branchi` for some `i`, or an iterated `BranchiTypej` for some `i` and `j`, then each branch, say `Branchi` is translated as follows. + +- If `Branchi` has no fields i.e., `| Branchi`, then the corresponding Typescript type's union member is + + ```ts + | { name: 'Branchi' } + ``` + +- If `Branchi` has one or more fields i.e., `| Branchi BranchiType1 ... BranchiTypeMi`, then the corresponding Typescript type's union member is + + ```ts + | { name: 'Branchi' + , fields: + } + ``` + + where `` denotes the right hand side of the [product translation](#product-types) of `prod FieldsProduct = BranchiType1 ... BranchiTypeMi`. + + So, for example, given `| Branchi BranchiType1`, the corresponding Typescript type is as follows + + ```ts + | { name: 'Branchi' + , fields: BranchiType1 + } + ``` + + And given `| Branchi BranchiType1 BranchiType2`, the corresponding Typescript type is as follows. + + ```ts + | { name: 'Branchi' + , fields: [BranchiType1, BranchiType2] + } + ``` + +## Record types +The types `Document` and `Chapter` have been declared as record types in the LambdaBuffers schema using the `record` keyword. + +Record types are mapped to [object types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#object-types) in Typescript. +Given a LambdaBuffers' record type as follows + +```purescript +record MyRecord = { field1: SomeType1, ..., fieldN: SomeTypeN } +``` + +where `...` denotes iterated `fieldi: SomeTypei` for some `i`, the corresponding Typescript type is + +```ts +type MyRecord = { field1: SomeType1, ..., fieldN, SomeTypeN } +``` + +## Type classes quickstart + +Typescript has no builtin implementation of type classes. As such, LambdaBuffers rolled its own type classes. +A complete usage example can be found in the [Typescript Prelude sample project](./typescript-prelude/src/index.mts), but assuming the packaging is setup correctly, the interface to use a typeclass is as follows + +```ts +import * as LbrPrelude from "lbr-prelude"; + +// In Haskell, this is `10 == 11` +LbrPrelude.Eq[LbrPrelude.Integer].eq(10n, 11n) // false + +// In Haskell, this is `Just 3 == Nothing` +LbrPrelude.Eq[LbrPrelude.Maybe](LbrPrelude.Eq[LbrPrelude.Integer]) + .eq( { name: 'Just', fields: 3 } + , { name: 'Nothing' }) // false +``` + +In particular, we access a global variable `LbrPrelude.Eq` which contains the type class instances, and pick out a particular instance with the type's name like `LbrPrelude.Integer`. Note that the `LbrPrelude.Maybe` instance requires knowledge of the `Eq` instance of the `LbrPrelude.Integer`, so we must pass that in as a function argument. + +## Type classes in detail + +A type class in Typescript is an object type which defines a set of methods. +For example, the `Eq` type class in Haskell defines the set of methods `==` (equality) and `/=` (inequality) as follows. + +```haskell +class Eq a where + (==) :: a -> a -> Bool + (/=) :: a -> a -> Bool +``` + +The corresponding [`Eq` class](https://github.com/mlabs-haskell/prelude-typescript/blob/main/src/Lib/Eq.ts) in Typescript is: + +```ts +export interface Eq { + readonly eq: (l: Readonly, r: Readonly) => boolean; + readonly neq: (l: Readonly, r: Readonly) => boolean; +} +``` + +Each type class in Typescript must have an associated global variable which maps unique representations of its instance types to the corresponding object of the type class implementation. +For example, the `Eq` type class has the [global variable](https://github.com/mlabs-haskell/lambda-buffers/blob/main/runtimes/typescript/lbr-prelude/src/LambdaBuffers/Eq.ts#L11) defined in the [lbr-prelude](https://github.com/mlabs-haskell/lambda-buffers/tree/main/runtimes/typescript/lbr-prelude) library defined as follows + +```ts +export const Eq: EqInstances = { } as EqInstances +``` + +where `EqInstances` is an interface type that is initially empty but will be extended with instances of types later. + +```ts +export interface EqInstances { } +``` + +Finally, the following invariant is maintained in the code generator: + +- Every type `T` has an associated unique [symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) also called `T`. + +So, the type `Integer` has + +```ts +export type Integer = bigint +export const Integer: unique symbol = Symbol('Integer') +``` + +and implementing its `Eq` instance amounts to the following code. + +```ts +export interface EqInstances { + [Integer]: Eq +} +Eq[Integer] = { eq: (l,r) => l === r + , neq: (l,r) => l !== r + } +``` + +For types defined in the LambdaBuffers schema, this chunk of code will be automatically generated provided there is an appropriate `derive` construct. + +### Type instances with constraints +Recall in Haskell that the `Eq` instance for a tuple may be defined as follows + +```haskell +instance (Eq a, Eq b) => Eq (MyPair a b) where + MyPair a1 a2 == MyPair b1 b2 = a1 == b1 && a2 == b2 + MyPair a1 a2 != MyPair b1 b2 = a1 != b1 || a2 != b2 +``` + +The corresponding Typescript type definition and instance would be defined as follows + +```ts +export type MyPair = [a, b] +export const MyPair: unique symbol = Symbol('MyPair') + +export interface EqInstances { + [MyPair]: (a : Eq, b : Eq) => Eq> +} +Eq[MyPair] = (dictA, dictB) => { return { eq: (a,b) => dictA.eq(a[0], b[0]) && dictB.eq(a[1], b[1]) + , neq: (a,b) => dictA.neq(a[0], b[0]) || dictB.neq(a[1], b[1]) + } + } +``` + +Note that the constraints `(Eq a, Eq b) =>` become arguments `dictA` and `dictB` that are used to construct the `Eq` instance for `MyPair`. + +This loosely follows the original translation given in the paper [How to make ad-hoc polymorphism less ad hoc](https://dl.acm.org/doi/10.1145/75277.75283) with some minor modifications. + +## Limitations + +- Only Haskell 2010 typeclasses are supported for the Typescript code generator. So, the following schemas will probably generate incorrect code. + + ```purescript + derive Eq (MyPair a a) + derive Eq (MyMaybe (MyMaybe Integer)) + derive Eq (MyMaybe Integer) + ``` diff --git a/extras/lbf-nix/build.nix b/extras/lbf-nix/build.nix index f0796604..c6f5f69a 100644 --- a/extras/lbf-nix/build.nix +++ b/extras/lbf-nix/build.nix @@ -1,4 +1,4 @@ -{ inputs, ... }: { +{ lib, inputs, ... }: { perSystem = { pkgs, config, system, ... }: let lbg-haskell = "${config.packages.lbg-haskell}/bin/lbg-haskell"; @@ -23,7 +23,7 @@ lbfTypescript = opts: import ./lbf-typescript.nix { inherit pkgs lbg-typescript; - inherit (config.packages) lbf; + inherit (config.packages) lbf-list-modules-typescript lbf; inherit (inputs.flake-lang.lib.${system}) typescriptFlake; } opts; @@ -31,7 +31,7 @@ import ./lbf-prelude-typescript.nix { inherit pkgs lbg-typescript config; - inherit (config.packages) lbf; + inherit (config.packages) lbf-list-modules-typescript lbf; inherit (inputs.flake-lang.lib.${system}) typescriptFlake; } opts; @@ -39,7 +39,7 @@ import ./lbf-plutus-typescript.nix { inherit pkgs lbg-typescript config; - inherit (config.packages) lbf; + inherit (config.packages) lbf-list-modules-typescript lbf; inherit (inputs.flake-lang.lib.${system}) typescriptFlake; } opts; @@ -48,5 +48,18 @@ lbfPlutusRust = import ./lbf-plutus-rust.nix pkgs config.packages.lbf lbg-rust; }; + packages = { + lbf-list-modules-typescript = pkgs.stdenv.mkDerivation { + name = "lbf-list-modules-typescript"; + buildInputs = [ pkgs.nodejs ]; + dontUnpack = true; + src = ./lbf-list-modules-typescript; + installPhase = '' + mkdir -p "$out/bin" + cp "$src" "$out/bin/lbf-list-modules-typescript" + ''; + }; + }; + }; } diff --git a/extras/lbf-nix/lbf-list-modules-typescript b/extras/lbf-nix/lbf-list-modules-typescript new file mode 100755 index 00000000..876b09cd --- /dev/null +++ b/extras/lbf-nix/lbf-list-modules-typescript @@ -0,0 +1,70 @@ +#!/usr/bin/env node + +const fs = require('node:fs/promises') +const process = require('node:process') + +const help =`Usage: lbf-list-modules-typescript =... + +Description: + Given a sequence of \`pkg-name1=dir1\`, ..., \`pkg-nameN=dirN\`, outputs + a JSON object on stdout of the form + \`\`\` + { + "pkg-name1": lbfs1, + ... + "pkg-nameN": lbfsN + } + \`\`\` + where for every \`i\`, each \`lbfi\` is a list of all paths ending with \`*.lbf\` in \`diri\` + except the \`*.lbf\` suffix is removed and all \`/\`s are replaced with \`.\`. + + When there are no arguments, this returns the empty JSON object. + +Examples: + Suppose there is a directory as follows. + \`\`\` + . + |-A.lbf + \`-Directory + |-B.lbf + \`-C.lbf + \`\`\` + Then, \`lbf-list-modules-typescript my-package=.\` returns + \`\`\` + { + "my-package": [ "A", "Directory.B", "Directory.C" ] + } + \`\`\`` + +async function main() { + const argv = process.argv.slice(2); + + const result = { } + + for (const pkgDir of argv) { + const pkgDirMatches = pkgDir.match(/^(?[^=]*)=(?[^=]+)$/) + if (pkgDirMatches === null) + throw new Error("CLI argument not of the form \`=\`\n" + help) + + const { pkgName, dir } = pkgDirMatches.groups + const listings = await fs.readdir(dir, { recursive: true }) + + const filtered = [] + for (const listing of listings) { + const listingMatch = listing.match(/^(?.*)\.lbf$/) + if (listingMatch === null) + continue + const { moduleName } = listingMatch.groups + filtered.push(moduleName.replace(/\//g, '.')) + } + + if (result[pkgName] !== undefined) + console.error(`lbf-list-modules-typescript: warning: package name ${pkgName} is defined multiple times. Overwriting the first definition.`) + + result[pkgName] = filtered + } + + process.stdout.write(JSON.stringify(result)); +} + +main() diff --git a/extras/lbf-nix/lbf-plutus-typescript.nix b/extras/lbf-nix/lbf-plutus-typescript.nix index e5333d43..8a895e4f 100644 --- a/extras/lbf-nix/lbf-plutus-typescript.nix +++ b/extras/lbf-nix/lbf-plutus-typescript.nix @@ -4,12 +4,13 @@ , lbg-typescript , config , typescriptFlake +, lbf-list-modules-typescript }: lbfTypescriptOpts: let utils = import ./utils.nix pkgs; - lbfTypescript = import ./lbf-prelude-typescript.nix { inherit pkgs lbf lbg-typescript config typescriptFlake; }; + lbfTypescript = import ./lbf-prelude-typescript.nix { inherit pkgs lbf lbg-typescript config lbf-list-modules-typescript typescriptFlake; }; lbfTypescriptOptsForPlutus = utils.overrideAttrs { imports = { diff --git a/extras/lbf-nix/lbf-prelude-typescript.nix b/extras/lbf-nix/lbf-prelude-typescript.nix index 9a5ddb38..8d13c20f 100644 --- a/extras/lbf-nix/lbf-prelude-typescript.nix +++ b/extras/lbf-nix/lbf-prelude-typescript.nix @@ -9,11 +9,12 @@ , lbg-typescript , config , typescriptFlake +, lbf-list-modules-typescript }: lbfTypeScriptOpts: let utils = import ./utils.nix pkgs; - lbfTs = import ./lbf-typescript.nix { inherit pkgs lbf lbg-typescript typescriptFlake; }; + lbfTs = import ./lbf-typescript.nix { inherit pkgs lbf lbg-typescript typescriptFlake lbf-list-modules-typescript; }; lbfTypeScriptOptsForPrelude = utils.overrideAttrs { imports = { diff --git a/extras/lbf-nix/lbf-typescript.nix b/extras/lbf-nix/lbf-typescript.nix index 6bd188fa..af197f1c 100644 --- a/extras/lbf-nix/lbf-typescript.nix +++ b/extras/lbf-nix/lbf-typescript.nix @@ -8,6 +8,9 @@ , lbg-typescript # Function to create typescript flakes , typescriptFlake + # Function to create a JSON file which maps package names to lists of + # LambdaBuffers names +, lbf-list-modules-typescript }: @@ -85,15 +88,11 @@ lbfTypescriptOpts@{ let lbf-build = import ./lbf-build.nix pkgs lbf; - lbfListModules = pkgs.callPackage ./lbf-list-modules.nix { }; - - packageSet = - pkgs.writeTextFile { - name = "lb-typescript-packages"; - text = - builtins.toJSON - (builtins.mapAttrs (_name: lbfListModules) imports); - }; + packageSet = pkgs.runCommand "lb-typescript-packages" { buildInputs = [ lbf-list-modules-typescript ]; } + '' + 1>"$out" lbf-list-modules-typescript \ + ${pkgs.lib.escapeShellArgs ( pkgs.lib.mapAttrsToList (name: value: "${name}=${value}") imports) } + ''; lbfBuilt = with lbfTypescriptOpts; lbf-build.build diff --git a/lambda-buffers-codegen/app/Main.hs b/lambda-buffers-codegen/app/Main.hs index 2b70bcdd..6fbba1d2 100644 --- a/lambda-buffers-codegen/app/Main.hs +++ b/lambda-buffers-codegen/app/Main.hs @@ -119,7 +119,7 @@ typescriptGenOptsP = ) <*> strOption ( long "packages" - <> short 'g' + <> short 'p' <> metavar "FILEPATH" <> help "JSON file containing the package-set and all of its modules (including current package) with schema `{ [index : PackageName]: ModuleName[] }`." ) diff --git a/lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript/Print/Ty.hs b/lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript/Print/Ty.hs index 866be5f7..e1940091 100644 --- a/lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript/Print/Ty.hs +++ b/lambda-buffers-codegen/src/LambdaBuffers/Codegen/Typescript/Print/Ty.hs @@ -89,7 +89,10 @@ printSum pkgMap tyN (PC.Sum ctors _) = do return $ group $ if null ctors - then mempty + then -- TODO(jaredponn): is this what we want? The empty sum + -- corresponds to the `never` type in the sense that there are no + -- values which satisfy this type + "never" else align $ vsep $ map (pipe <+>) ctorDocs -- | The name of the field which specifies a type constructor diff --git a/lambda-buffers-frontend/build.nix b/lambda-buffers-frontend/build.nix index d9313a0c..3b1a9140 100644 --- a/lambda-buffers-frontend/build.nix +++ b/lambda-buffers-frontend/build.nix @@ -43,6 +43,9 @@ config.packages.lbf-plutus-to-purescript config.packages.lbf-prelude-to-rust config.packages.lbf-plutus-to-rust + config.packages.lbf-list-modules-typescript + config.packages.lbf-prelude-to-typescript + config.packages.lbf-plutus-to-typescript ]; }; };