{"id":18354859,"url":"https://github.com/voltrevo/valuescript","last_synced_at":"2025-04-15T07:54:24.266Z","repository":{"id":86118762,"uuid":"483222698","full_name":"voltrevo/ValueScript","owner":"voltrevo","description":"A dialect of TypeScript with value semantics.","archived":false,"fork":false,"pushed_at":"2024-07-25T02:27:12.000Z","size":1871,"stargazers_count":87,"open_issues_count":9,"forks_count":3,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-15T07:54:09.839Z","etag":null,"topics":["functional-programming","javascript","rust","typescript"],"latest_commit_sha":null,"homepage":"https://ValueScript.org","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/voltrevo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-19T11:45:06.000Z","updated_at":"2025-03-01T16:31:32.000Z","dependencies_parsed_at":"2023-10-30T06:33:34.935Z","dependency_job_id":"5de99b1c-01bd-4b4a-a83d-cef47cfba2b6","html_url":"https://github.com/voltrevo/ValueScript","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voltrevo%2FValueScript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voltrevo%2FValueScript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voltrevo%2FValueScript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/voltrevo%2FValueScript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/voltrevo","download_url":"https://codeload.github.com/voltrevo/ValueScript/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249031773,"owners_count":21201357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","javascript","rust","typescript"],"created_at":"2024-11-05T22:05:16.957Z","updated_at":"2025-04-15T07:54:24.250Z","avatar_url":"https://github.com/voltrevo.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ValueScript\n\nA dialect of TypeScript with value semantics.\n\n## [Playground](https://valuescript.org/playground)\n\n[Try ValueScript instantly using your web browser.](https://valuescript.org/playground)\n\n## About\n\nValueScript uses TypeScript syntax, but it compiles to a bytecode that runs in a\ndifferent virtual machine.\n\nThe syntax is identical, not just similar. We use [SWC](https://swc.rs/)'s\nTypeScript parser. This means you can use your IDE's TypeScript functionality\nwhen writing ValueScript.\n\nThis program shows the core difference between ValueScript and TypeScript:\n\n```ts\nexport default function main() {\n  const leftBowl = [\"apple\", \"mango\"];\n\n  let rightBowl = leftBowl;\n  rightBowl.push(\"peach\");\n\n  return leftBowl.includes(\"peach\");\n  // TypeScript:  true\n  // ValueScript: false\n}\n```\n\nIn TypeScript, `\"peach\"` is in the left bowl because TypeScript interprets\n`rightBowl = leftBowl` to mean that there is one bowl and both variables point\nto the same bowl. That one bowl is changed by `.push(\"peach\")`.\n\nIn ValueScript, objects never change this way, only variables change. Pushing\nonto `rightBowl` is interpreted as a change to the `rightBowl` variable itself,\nnot the data it points to.\n\nYou can\n[see this in the playground](https://valuescript.org/playground/#/tutorial/valueSemantics.ts),\nor run it locally:\n\n```sh\ngit clone git@github.com:voltrevo/ValueScript\ncd ValueScript\ncargo build -p vstc\nexport PATH=\"$PATH:$(pwd)/target/debug\"\nvstc run inputs/passing/readme-demo.ts\n```\n\nOne way to understand this is to imagine that things like `=` and passing\nparameters are implemented by deep copying. We don't implement it that way, but\nit would work the same if we did (just a lot slower).\n\nInstead, we implement `rightBowl = leftBowl` by sharing the memory, but there's\nalso a reference count attached to that memory. When `rightBowl` is mutated, the\nVM can see that it's using shared memory, and does some bookkeeping to represent\n`rightBowl`'s updated value without mutating the shared memory.\n\nBy the same token, if `leftBowl` had gone out of scope or was optimized away,\nthe VM would see that `rightBowl` has the only reference to that memory, and\nwould mutate it directly.\n\n## No Side Effects\n\nValueScript has no side effects, with two exceptions:\n\n1. You can (in future) choose to introduce side effects via foreign functions\n2. Bugs (please\n   [report them](https://github.com/voltrevo/ValueScript/issues/new))\n\nValueScript does this by behaving differently to TypeScript in three key ways:\n\n1. Value semantics (see [About](#about))\n2. Captured variables can't be mutated (mutation is otherwise encouraged)\n3. When `this` changes inside `obj.method()`, the updated `this` value is used\n   to mutate `obj` when the method returns\n\n(2) and (3) are described in more detail in\n[this article](https://voltrevo.medium.com/valuescripts-unique-strategy-for-preventing-side-effects-ac8133735a11).\n\n## Intended Usage\n\nValueScript has its roots in a programming school of thought that discourages\nthe use of mutation.\n\nWhile this idea is indeed useful, the result of this approach can only push the\nmutation to the edges of the program. At the edge, you need to interact with\nthings that are inherently mutable, like users. The code you write becomes a\nsubsystem of some larger framework that dictates the interface with the outside\nworld.\n\nThis is why we expect that ValueScript will be most useful as a tool within a\nTypeScript project, rather than an alternative to it. This way you can benefit\nfrom TypeScript's rich ecosystem to interact with users and external systems,\nand also have a clearly separated immutable subsystem to define the core of your\napplication.\n\nBecause ValueScript shares the same syntax as TypeScript, you'll be able to\ninline ValueScript code into TypeScript like this:\n\n```ts\nconst points = inlineValueScript(() =\u003e {\n  const x = [3, 5];\n  const y = x;\n  y[0]--;\n\n  return { x, y };\n});\n\n// ValueScript doesn't have console.log, but TypeScript does.\nconsole.log(points); // { x: [3, 5], y: [2, 5] }\n```\n\nAdditionally, ValueScript has benefits that make it a suitable target for\nsecondary storage. Rather than writing to a file, your ValueScript code can read\nand update persistent objects the same way it interacts with regular in-memory\nobjects.\n\n## Benefits\n\n\u003cdetails\u003e\n\u003csummary\u003eEliminate mutation bugs\u003c/summary\u003e\n\nMutating things across your program is frequently intended, but it's also\nfrequently unintended, causing bugs.\n\nThis is why you are usually encouraged not to mutate function arguments, among\nother things. Sometimes you'll see workaround like `const a = [...b];`. In\nValueScript, just write it the natural way.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e`const` means what you think it does\u003c/summary\u003e\n\nEver felt weird about using `const` in situations like this?\n\n```ts\nconst values = [];\n\nvalues.push(123);\n\nreturn items;\n```\n\nUs too. The reason is that, in a mutable world, it's the array that `values`\npoints to that is mutating. Pushing to that array doesn't change `values` - it\nstill points to the same array, right?\n\nIn ValueScript, it's not the same array, because arrays don't change. Instead,\nit is indeed the variable that changes, and therefore, if you mark it as\n`const`, attempting to do so is a compile-time error.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eTestable code\u003c/summary\u003e\n\nTesting code is all about being able to draw a boundary around something that\ncan be given inputs so that you can check its outputs against your expectations.\n\nBeing able to draw these boundaries is usually challenging in real-world\nsystems, because by default everything wants to connect to something tangible to\nserve its purpose as directly as possible. Most things that matter to you become\nuntested because of their coupling to externalities that are too difficult to\nmeaningfully replicate in a test case. Testing degrades into an inauthentic\nadd-on that focuses on trivialities.\n\nBy using ValueScript, you can maintain a clear separation between a domain that\nshould be easy to test - the core of what your application does, and a domain\nthat is difficult to test - how your application talks to the world.\n\nA ValueScript program is always a function that, when called with the same\ninputs, produces the same outputs.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNo garbage collection\u003c/summary\u003e\n\nIn ValueScript, it's impossible to create data that circularly references\nitself. This isn't because something is keeping watch and producing an error if\nyou do it accidentally. Rather, it's just an inherent consequence of how\nValueScript works:\n\n```ts\nlet x = {};\nx.x = x; // { x: {} }\n\n// (In TypeScript: { x: { x: { x: { x: { ... } } }} })\n```\n\nCircular references are the whole reason why garbage collectors are needed\n(assuming you want to reuse memory and don't want to figure out when it's safe\nto do so). Without them, ValueScript is able to simply keep a count of how many\nreferences each object has, and when that count drops to zero, it cleans up the\nmemory immediately.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePersistence\u003c/summary\u003e\n\nIn a traditional mutable program, the important entities in that program often\ncan't be stored authentically without also capturing the state of the entire\nprogram that contains them. Even when that isn't true, the entity needs to be\ntranslated into a form that can be stored in a process we know and love called\n_serialization_.\n\nValueScript is different. Everything can be persisted as its direct contents and\na recursive inclusion of its dependencies. This includes functions and class\ninstances (and the methods on those class instances). In ValueScript, everything\nis plain data.\n\nIn fact, because ValueScript doesn't require garbage collection, it's also\npossible to build up large structures that wouldn't fit into memory. In garbage\ncollected languages, the garbage collector needs to be able to fully traverse\nall the data (as a last resort) to find cycles to clean up, so growing beyond\nmemory limits isn't very practical. ValueScript doesn't have this limitation.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMake use of TypeScript's type checking\u003c/summary\u003e\n\nValueScript is similar enough to TypeScript that the type checker correctly\nidentifies type errors in ValueScript.\n\nIn fact, when the differences matter, the type checker often actually favors\nValueScript, not TypeScript.\n\nE.g.\n\n```ts\nlet a: { value?: string | number } = {};\na.value = \"str\";\n\nlet b = a;\nb.value = 37;\n\ntype T = typeof a.value;\n//              ~~~~~~~ TypeScript: 37\n//              ~~~~~~~ ValueScript: \"str\"\n\n// The type checker assigns `string` to `T`.\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eConcurrency\u003c/summary\u003e\n\ntl;dr:\n\n- (This is not implemented yet)\n- ValueScript is multi-threaded\n- Calling an `async` function creates a new thread\n- Because ValueScript functions are pure (async or not), the concurrent\n  evaluation is guaranteed to be the same as sequential evaluation (ie no race\n  conditions)\n- You can write `value = promise.wait()` in a sync function, because this\n  doesn't block other threads from running\n\nBy using value semantics, ValueScript ensures that a function, called with the\nsame arguments, always returns the same value (except for any side effects you\nchoose to introduce with foreign functions). This includes instance methods by\nconsidering the instance data to be one of the arguments.\n\nThis means that once a function has its arguments, its result is fully\ndetermined. It would be safe to evaluate the function concurrently because its\noutput cannot be affected by other code:\n\n```ts\nconst f = (z: number): number =\u003e {\n  const x = widget.calculate(37);\n  const y = expensiveCalculation(z, z);\n\n  return x + y;\n};\n```\n\nAbove, `widget` is captured by `f`. ValueScript requires that captured variables\nare `const`, which means that `widget` cannot change, and therefore\n`widget.calculate(37)` cannot change. This means that the value of `f(z)` is\nindependent of any other work that happens in our program.\n\nTherefore, we could safely evaluate `f(z)` concurrently. In future, some\ncalculations might automatically be upgraded to concurrent execution, but\nknowing when it is worthwhile to create a separate thread is a complex and\ninexact science.\n\nInstead, in the foreseeable future, ValueScript will allow concurrent evaluation\nof `async` functions. Even if `f` isn't already `async`, you could evaluate it\nconcurrently like this:\n\n```ts\nconst fPromise = (async () =\u003e f(z))();\n```\n\nAlternatively, something like `vs.thread` could make this more clear:\n\n```ts\nconst fPromise = vs.thread(() =\u003e f(z));\n```\n\nOf course, functions like `f` could be made `async` to begin with, to signal the\nintent that they are expensive calculations that justify a thread:\n\n```ts\nconst f = async (z: number): Promise\u003cnumber\u003e =\u003e {\n  const x = widget.calculate(37);\n  const y = expensiveCalculation(z, z);\n\n  return x + y;\n});\n```\n\nNow `f` just returns a promise:\n\n```ts\nconst fPromise = f(z);\n```\n\nLater, when you need the value inside `fPromise`, you can use `await` as normal:\n\n```ts\nconst fValue = await fPromise;\n```\n\nHowever, this requires you to be inside an `async` function.\n\nIn JavaScript, it would be a big no-no to allow a method that synchronously\nextracts the value of a promise by blocking evaluation until it became\navailable. This is because JavaScript is single-threaded, and there's usually\nother work the runtime could be doing.\n\nIn ValueScript, the other work happens in other threads, so there's no reason to\nprohibit it. ValueScript allows this via `promise.wait()`:\n\n```ts\nconst fValue = f(z).wait();\n```\n\nSuppose instead that `f`, `widget.calculate`, and `expensiveCalculation` are all\nsync functions. Suppose that `f` is part of an important API - it has users and\nyou can't require them to make changes. Allowing `.wait` means those users can\nstill benefit from this multi-threaded version of `f`:\n\n```ts\nconst f = (z: number): number =\u003e {\n  const [x, y] = Promise.all([\n    vs.thread(() =\u003e widget.calculate(37)),\n    vs.thread(() =\u003e expensiveCalculation(z, z)),\n  ]).wait();\n\n  return x + y;\n};\n```\n\nYou could also simplify code like `f` with a utility like `parallel`:\n\n```ts\n// Simple version that unnecessarily widens T when the jobs return different\n// types\nfunction parallel\u003cT\u003e(...jobs: (() =\u003e T)[]): T[] {\n  return Promise.all(jobs.map(vs.thread)).wait();\n}\n\nconst f = (z: number): number =\u003e {\n  const [x, y] = parallel(\n    () =\u003e widget.calculate(37),\n    () =\u003e expensiveCalculation(z, z),\n  );\n\n  return x + y;\n};\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eStatic Analysis \u0026 Optimization\u003c/summary\u003e\n\nValueScript dramatically expands the cases where program behavior can be\ndetermined statically. In traditional languages, inferences about data in\nvariables are quickly lost because it is impossible to know whether some other\ncode might modify that data.\n\nA relatively simple application of this is tree-shaking. ValueScript analyzers\nwill be able to determine much more accurately what code is actually used, and\nonly include that code for distribution. During development you can also get a\nlot more feedback like 'this statement has no effect'.\n\nAnother important use-case here is testing. In the future, ValueScript will\ninclude `vs.staticTest(name, fn)` which accepts a function taking no arguments,\nwhich can therefore be computed statically. The compiler will emit an error if\nthe test fails.\n\n\u003c/details\u003e\n\n## Status\n\nValueScript is in early development. There may be some descriptions of\nValueScript elsewhere here that represent how ValueScript is intended to work,\nnot the subset of ValueScript that has actually been implemented.\n\n\u003cdetails\u003e\n\u003csummary\u003eImplemented\u003c/summary\u003e\n\n- `console.log`\n- Classes\n- Closures\n- Loops\n- Recursion\n- Destructuring\n- Exceptions\n  - Variables changed during try block are reverted on catch\n- Enforcing `const`\n- Temporal dead zones\n- Local imports\n  - Including the many various import and export patterns\n- Tree shaking\n- Copy-on-write optimizations\n- utf8 strings (_not_ JS's utf16 strings)\n  - `\"🫣\".length -\u003e 4`\n  - (JS: `-\u003e 2`)\n  - `[0, 1, 2, 3, 4].map(i =\u003e \"🫣\"[i]) -\u003e [\"🫣\", \"\", \"\", \"\", undefined]`\n  - (JS: `-\u003e [\"\\ud83e\", \"\\udee3\", undefined, undefined, undefined]`)\n- `Math`\n- Array standard methods (`.sort`, `.map`, `.filter`, etc.)\n- Most string standard methods (`.includes`, `.slice`, `.split`, etc.)\n- BigInt\n- Iterators\n- Spread operator on iterables\n- Generators\n- Structural comparison\n  - `{} === {} -\u003e true`\n  - `new Point(1, 2) === new Point(1, 2)`\n  - `(() =\u003e {}) === (() =\u003e {})`\n  - JS: `-\u003e false`\n  - This is a value semantics thing - objects don't have identity\n- TypeScript enums\n- TypeScript parameter properties\n- Capturing `this` in arrow functions\n- Many unusual JS things:\n  - `[] + [] -\u003e \"\"`\n  - `[10, 1, 3].sort() -\u003e [1, 10, 3]`\n  - `\"b\" + \"a\" + +\"a\" + \"a\" -\u003e \"baNaNa\"`\n  - (With few exceptions like utf8, the goal is to just do things the JS way, to\n    maximize familiarity for people coming from JS. We're open to revising this\n    strategy, subject to\n    [community feedback](https://github.com/voltrevo/ValueScript/issues/new).)\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNot yet implemented\u003c/summary\u003e\n\n**Ecosystem**\n\n- Foreign functions\n- Standardized foreign function packages for web/node/deno-like APIs\n  - Sadly, the only access to the host environment is currently `console.log`\n  - We consider this extremely important, but want the language itself to be\n    more robust before embarking on this enormous category of work\n  - (Some small \u0026 strategic host access will probably be implemented earlier)\n- Tools for embedding ValueScript in other languages\n  - (The playground kinda does this, but the solution is purpose-built for the\n    playground, and not intended to be used in other projects (you're welcome to\n    try it of course, but better solutions are planned))\n  - Webpack integration\n    - `import immutableStuff from \"ValueScript:./path/to/immutableStuff\";`\n  - Building JavaScript bundles containing ValueScript via embedded WebAssembly\n    (or importing the required WebAssembly)\n  - Transpiling ValueScript into JavaScript\n    - E.g. `a.b.c++` -\u003e `a = { ...a, b: { ...a.b, c: a.b.c + 1 } }`\n  - `inlineValueScript(() =\u003e { /* ValueScript */ })`\n    - Uses `.toString()` to get the source code and compiles and runs it in\n      WebAssembly\n  - C libraries, and bindings for python etc\n- Dynamic imports\n- Importing modules from npm\n  - (Even when this is implemented, many modules won't work due to their\n    intention to run in a JS environment though. At least at first.)\n\n**Core**\n\n- Object spreading\n- Rest params\n- Async functions\n- TypeScript namespaces\n- `import.meta`\n- Unusual JS things like passing unintended types to standard functions\n- A workaround for JavaScript's utf16 strings\n  - `jsˋ🫣ˋ.length -\u003e 2`\n  - `[0, 1, 2].map(i =\u003e jsˋ🫣ˋ[i]) -\u003e [jsˋ\\ud83eˋ, jsˋ\\udee3ˋ, undefined]`\n  - (To be fair to js, note that iteration uses code points:\n    `[...jsˋ🫣🚀ˋ] -\u003e [jsˋ🫣ˋ, jsˋ🚀ˋ]`)\n- JSX\n- Regex\n- Date\n- Stack traces\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNot planned\u003c/summary\u003e\n\n- Prototype pollution\n- Mutating imported variables\n- Reference semantics\n- Mutating captured variables\n- \"Everything is an object\"\n  - Properties cannot be set on non-objects like arrays and functions\n  - `new Number()` throws a `TypeError` instead of creating a non-primitive\n    number-like thing\n- The `with` keyword\n- utf16-based operations on native strings\n  - (But see `jsˋˋ` workaround in not-yet section)\n- `Math.random` (except as an opt-in foreign function)\n- `Date.now` (except as an opt-in foreign function)\n\n\u003c/details\u003e\n\n## Contributing\n\nWe'd be thrilled to have your help! Please see\n[CONTRIBUTING.md](CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoltrevo%2Fvaluescript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvoltrevo%2Fvaluescript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvoltrevo%2Fvaluescript/lists"}