# Introducing calcit-js: toy language inspired by cljs

**URL:** https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097
**Category:** Showcase
**Created:** [January 29, 2021, 9:31am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097 "2021-01-29T09:31:38Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [January 29, 2021, 9:31am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/1 "2021-01-29T09:31:38Z")

</div>

Although I don’t use parentheses in calcit, it’s still mostly inspired by ClojureScript. The project is called [calcit-runner](https://github.com/calcit-lang/calcit-runner) since at first it was only designed be an interpreter, now it also emits JavaScript.

### Some demos

To give a demo, there’s a linux binary called `cr_once` released at [http://bin.calcit-lang.org/](http://bin.calcit-lang.org/) , which can be run like:

```auto
=>> cr_once -e:"range 10"
Running calcit runner(0.2.34) in CI mode
(0 1 2 3 4 5 6 7 8 9)
=>> cr_once -e:"->> (range 4) $ map $ fn (x) (* x x)"
Running calcit runner(0.2.34) in CI mode
(0 1 4 9)

```

you may find functions and macros familiar if you see the docs [http://apis.calcit-lang.org/](http://apis.calcit-lang.org/) . And this docs itself is built with calcit-js, roughly 5x the cost comparing its ClojureScript version.

A more complicated demo is to run a script. In calcit, it’s using a snapshot file, rather “files of source”. Just one snapshot containing multiple namespaces of this package, and using indentation based syntax:

```cirru
{} (:package |app)
  :configs $ {} (:init-fn |app.main/main!) (:reload-fn |app.main/reload!)
  :files $ {}
    |app.main $ {}
      :ns $ quote
        ns app.main $ :require
      :defs $ {}
        |main! $ quote
          defn main! ()
            println "\"Loaded program!"
            ; try-fibo
            echo $ sieve-primes ([] 2 3 5 7 11 13) 17 400

        |reload! $ quote
          defn reload! () nil

        |sieve-primes $ quote
          defn sieve-primes (acc n limit)
            if (&> n limit) acc $ if
              every?
                fn (m)
                  &> (mod n m) 0
                , acc
              recur (conj acc n) (inc n) (, limit)
              recur acc (inc n) limit

```

```auto
=>> cr_once fibo.cirru
Running calcit runner(0.2.34) in CI mode
Runner: specifying filesfibo.cirru
Calcit runner version: 0.2.34
Loaded program!
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397)

```

Since Nim compiles to C for running, it boots quite fast:

```auto
=>> time cr_once fibo.cirru
Running calcit runner(0.2.34) in CI mode
Runner: specifying filesfibo.cirru
Calcit runner version: 0.2.34
Loaded program!
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397)

real	0m0.122s
user	0m0.109s
sys	0m0.010s

```

By default it uses the file called `compact.cirru` which is emitted from calcit-editor.

And to emit js:

```bash
cr_once --emit-js fibo.cirru

```

The code generated from the command-line looks like(I formatted it with Pretter, manually):

```js
import * as $calcit from "./calcit.core";

export let sieve_DASH_primes = $calcit.wrapTailCall(function sieve_DASH_primes(
  acc,
  n,
  limit
) {
  return $calcit._AND__GT_(n, limit)
    ? acc
    : $calcit.every_QUES_(function f_PCT_(m) {
        return $calcit._AND__GT_($calcit.mod(n, m), 0.0);
      }, acc)
    ? $calcit.recur($calcit.conj(acc, n), $calcit.inc(n), limit)
    : $calcit.recur(acc, $calcit.inc(n), limit);
});
export function main_BANG_() {
  $calcit.println("Loaded program!");
  /* (try-fibo) */ null;
  return $calcit.echo(
    sieve_DASH_primes(
      $calcit._LIST_(2.0, 3.0, 5.0, 7.0, 11.0, 13.0),
      17.0,
      400.0
    )
  );
}
export function reload_BANG_() {
  return null;
}

```

Also notice that an extra `calcit.core.js` is generated containing some core functions. And an extra package `@calcit/procs` is required to run the code.

Maybe you could be familiar with macros like [`->>`](https://github.com/calcit-lang/calcit-runner/blob/master/src/includes/calcit-core.cirru#L240):

```cirru
defmacro ->> (base & xs)
  if (empty? xs)
    quote-replace ~base
    &let
      x0 (first xs)
      if (list? x0)
        recur (append x0 base) & (rest xs)
        recur ([] x0 base) & (rest xs)

```

The binaries are not quite ready. I develop it with Macos and it probably requires some Nim and Webpack knowledge to run it on other platforms.

### Motivations and calcit-editor

It’s called “calcit-runner” because it based on and also cooperated with my previous project Calcit Editor which was used to generate ClojureScript:

> [@A piece of intro to Calcit Editor](https://v2.clojureverse.org/t/a-piece-of-intro-to-calcit-editor/5604):
>
> It’s an old project from me and it can be traced back to 2015 when I was generating JavaScript from S-Expressions. Now I’m just trying to update the README a bit so that people might get to know what I was trying to acheive easier. It started with a simple idea: what if I just write code and let the editor handling layout for me? You might have seen other answers, Calcit Editor is my answer: Calcit Editor Intuitive S-expressions editing for Clojure(Script). Auto Layout: expressions in b…

Calcit Editor uses `calcit.cirru` as snapshot file, and now supports emitting `compact.cirru` and `.compact-inc.cirru` as well. calcit-runner makes use of these 2 files. File file `.compact-inc.cirru` is for handling incremental changes.

While it’s cool to have ClojureScript with immutable data and macros, I do want to narrow the gap between “immutable data”-macros combo and JavaScript ecosystem. I learnt a lot about ClojureScript in the past 5 years, however it still bothers me in some aspects:

- JVM compiler starts slowly and eats lots of memory of my laptops. thheller helped a lot but JVM has its own limitations.
- ClojureScript has its own semantics because Clojure has the semantics, then we can’t change that for the reason that JavaScript has its own ecosystem, an always changing ecosystem.

I’m still using ClojureScript. Just want to try more possibilities. And I always thought in `calcit.cirru` I already have a lot of information of the program, why do I have to run it by compiling to another language first. So `calcit-runner` was the project to run Calcit Editor’s snapshot file.

### Calcit Runner features

To make it kind of compatible to ClojureScript, I need some basic features:

- macros, then I can generate syntax like in Clojure,
- persistent data, which is at Clojure’s core semantics,
- hot code replacement, for fast feedback loops,
- frequently used APIs like in ClojureScript.

and being a JavaScript programmer there are still many features I can’ handle. So some major features you see in Clojure are missing in calcit-runner. It does not have a REPL, it does not support threads, no try/catch, no async programming. Or it’s currently more like a calculator.

However since macros are supported, emitting JavaScript is a lot easier after all syntax sugars are expanded.

The experience using calcit-runner is like something in between shadow-cljs and webpack, I modify the code, code got replaced in a browser(pure functions and atom states), an `after-load` function triggered, I run snippets in Chrome Console and use js debugger.

Meanwhile I can use just use calcit-runner to run code on top of Nim, save snapshot file again, it replaces code, and trigger `after-load` function as well, only got logs of error messages for debugging.

### How calcit-js is implemented

Similar to Clojure, when calcit-runner is interpreting a syntax tree, it,

- first, run preprocess to resolve symbols and expand macros,
- then code are turned into core syntax with platform functions,
- then platform function are called:

those phases can be seen in some of the files:

- Core macros and aliases: [calcit-runner.nim/src/includes/calcit-core.cirru at master · calcit-lang/calcit-runner.nim · GitHub](https://github.com/calcit-lang/calcit-runner/blob/master/src/includes/calcit-core.cirru)
- Syntaxes: [https://github.com/calcit-lang/calcit-runner/blob/master/src/calcit\_runner/core\_syntax.nim#L118-L127](https://github.com/calcit-lang/calcit-runner/blob/master/src/calcit_runner/core_syntax.nim#L118-L127)
- Functions: [calcit-runner.nim/src/calcit\_runner/core\_func.nim at master · calcit-lang/calcit-runner.nim · GitHub](https://github.com/calcit-lang/calcit-runner/blob/master/src/calcit_runner/core_func.nim#L1298-L1403)

Roughly, preprocessing(resolving and macro expansion) and evaluating are 2 steps. So the preprocessing step can also be used to support code emitting.

An feature in calcit-runner is, after preprocessing, the information of the program can be exported into a JSON file [https://gist.github.com/jiyinyiyong/b15ab9fa4467b8f8e09b5a291c84f655](https://gist.github.com/jiyinyiyong/b15ab9fa4467b8f8e09b5a291c84f655) . Only a demo though.

Meanwhile, the information is just ready for code generation, generating JavaScript in this case: [calcit-runner.nim/src/calcit\_runner/codegen/emit\_js.nim at master · calcit-lang/calcit-runner.nim · GitHub](https://github.com/calcit-lang/calcit-runner/blob/master/src/calcit_runner/codegen/emit_js.nim)

Since syntaxes are de-sugared, its core is quite tiny. Although currently calcit-js lacks many features of ClojureScript or JavaScript, it’s still powerful enough for running a virtual DOM library to render simple pages, such as the [APIs index](http://apis.calcit-lang.org/) above.

At current, calcit-js only support 2 simple rules of importing npm(or other js modules): `:as` and `:refer`, not renaming or handing `.default`. It just emits files with import/export syntax, relies on a extra bundler for running.

For tail recursions, calcit-js uses an extra function for handling `Recur` type from runtime, so does calcit-runner. It works well but comparing to code optimized from ClojureScript which uses `while(...){...}`, it could be a lot slower. calcit-js has bad compiler optimizations, it’s `let` is implemented with function wrappers so it’s far from optimized.

### Expectation of calcit-js

Most time in my jobs I have to deal with npm packages and mostly with Webpack. Since React apps relies on both immutable data and packages from npm, I do want my virtual DOM library to integrate into JavaScript system well.

This is how I want to use calcit-js:

- it compiles to js files with ES6 import/export syntax,
- a bundle probably Webpack, loads files and bundles it with npm dependencies.
- when a file changed, the bundler processes hot module replacement like a normal js file.

Besides Webpack, it works with Vite too, so I can debug code with code that’s less complicated, since browsers can load each `*.mjs` file without bundling.

It may also work in Node.js without bundling since Node.js can load modules in ES6 import/export syntax. It’s not quite smooth since it required `.mjs` extensions at current, and calcit-js need to handle those file extensions specifically.

The core functions of the runtime(except that `calcit.core.js` is emitted together with the program), are maintained with 2 npm packages so that bundler can just recognize:

- [https://www.npmjs.com/package/@calcit/procs](https://www.npmjs.com/package/@calcit/procs)
- [https://www.npmjs.com/package/@calcit/ternary-tree](https://www.npmjs.com/package/@calcit/ternary-tree)

At first I thought a language with immutable data and macros can hardly be compiled to js, but WISP did, Bucklescript did, that are proved to be possible. It’s just possible. ClojureScript has too many factors preventing it from integrating into JavaScript ecosystem comparing to other compile-to-js languages.

After all calcit-js only experimented a small area of integrating persistent data and macros into npm ecosystem. I’m fully expecting ClojureScript to find its way to connect with ES modules as well.(well, just please make JVM a little faster in doing that).

### Optimizations of persistent data in calcit-js

During development of calcit-runner, there are several bottle necks of performance(besides my poor experience on system programming):

- macro expanding
- persistent data
- variadic arguments

Macro expanding is still slow in my current implementation. I’m not going to discuss it since it involved too many factors.

For persistent data, I posted an explanation before(related to `List` and `Map`, not `Set` yet):

> [@Ternary-tree: structure sharing data for learning purpose](https://v2.clojureverse.org/t/ternary-tree-structure-sharing-data-for-learning-purpose/6760):
>
> This is a Nim project but the idea is mainly in the scope of Clojure’s persistent data structure. I was alway curious about the details of persistent vectors. And thanks to the excellent post of [Understanding Clojure’s Persistent Vectors, pt. 1](https://hypirion.com/musings/understanding-persistent-vector-pt-1) a lot of the details is revealed. I was mostly using JavaScript and don’t have much experience in C or Java, so I choose Nim for my toy and Nim has comparable(kind of) performance to C. Nim has a builtin but optional GC, which makes my at ease. Clojur…

To make it work in calcit-js, I finally turned in into a TypeScript project and trying carefully to reduce unnecessary memory usages:

> **[GitHub - calcit-lang/ternary-tree.ts: TypeScript version of calcit-lang/ternary-tree](https://github.com/calcit-lang/ternary-tree.ts)**
>
> TypeScript version of calcit-lang/ternary-tree

It’s not very optimal but since it’s designed to be a shared list, it has some benefits:

- just a `List`, not a `List` and a `Vector`. I don’t need to use `into []` to convert type in my program.
- `slice`, `assoc`, `dissoc` operation salso shares parts of the tree, that’s cheaper.(bad part is `get` got slower.)

Some other issues are related to the “variadic arguments” feature. In Clojure, we have “variadic functions”. But in JavaScript, it’s using “arguments spreading”. And in calcit-runner, it’s more like JavaScript:

```cirru
defn f1 (x0 & body)
  ; TODO x0 body

println (f1 1 2 3 4 5)

; or
def ys ([] 1 2 3 4 5)
println (f1 & ys))

```

While it may feels more natural to us JavaScript programmers(lack of static analysis though), it does bring performance costs. I located the costs in Nim implementation of calcit-runner. And it also brought barriers in using `List` in calcit-js for arguments spreading.

So to optimize that, I decided to implement `CrDataList` in two modes, one with `Array` and one with `TernaryTreeList`. When a `CrDataList` is first initialized, it’s an `Array`, so it’s fast for spreading. In `Array` mode, `get` is just JavaScript array accessing, that’s fast. And for `slice`, there’s virtual slicing by sharing same reference of original `Array`, so it’s cheap. It’s only turned into `TernaryTreeList`(which is a tree structure of persistent data) when it has to:

```js
class CrDataList {
  value: TernaryTreeList<CrDataValue>;
  // array mode store bare array for performance
  arrayValue: Array<CrDataValue>;
  arrayMode: boolean;
  arrayStart: number;
  arrayEnd: number;
  cachedHash: Hash;
  constructor(value: Array<CrDataValue> | TernaryTreeList<CrDataValue>) {
    if (Array.isArray(value)) {
      this.arrayMode = true;
      this.arrayValue = value;
      this.arrayStart = 0;
      this.arrayEnd = value.length;
      this.value = null;
    } else {
      this.arrayMode = false;
      this.value = value;
      this.arrayValue = [];
      this.arrayStart = null;
      this.arrayEnd = null;
    }
  }
  turnListMode() {
    if (this.arrayMode) {
      this.value = initTernaryTreeList(
        this.arrayValue.slice(this.arrayStart, this.arrayEnd)
      );
      this.arrayValue = null;
      this.arrayStart = null;
      this.arrayEnd = null;
      this.arrayMode = false;
    }
  }
  len() {
    if (this.arrayMode) {
      return this.arrayEnd - this.arrayStart;
    } else {
      return listLen(this.value);
    }
  }
  get(idx: number) {
    if (this.arrayMode) {
      return this.arrayValue[this.arrayStart + idx];
    } else {
      return listGet(this.value, idx);
    }
  }
 // more....

```

A similar trick is also added to `merge` function. When two Maps are being merged, they are not merged into a single `TernaryTreeList` directly, but instead saved in a linked list, until they have to be merged:

```js
class CrDataMap {
  cachedHash: Hash;
  chain: MapChain;
  depth: number;
  skipValue: CrDataValue;
  constructor(value: TernaryTreeMap<CrDataValue, CrDataValue>) {
    this.chain = { value: value, next: null };
    this.depth = 1;
    this.skipValue = fakeUniqueSymbol;
  }
  turnSingleMap() {
    if (this.depth === 1) {
      return;
    }
    // squash down to a single level of map
    let ret = this.chain.value;
    let cursor = this.chain.next;
    while (cursor != null) {
      if (!isMapEmpty(cursor.value)) {
        ret = ternaryTree.mergeSkip(cursor.value, ret, this.skipValue);
      }
      cursor = cursor.next;
    }
    this.chain = {
      value: ret,
      next: null,
    };
    this.depth = 1;
  }
  len() {
    this.turnSingleMap();
    return mapLen(this.chain.value);
  }
  get(k: CrDataValue) {
    let cursor = this.chain;
    while (cursor != null) {
      let v = mapGet(cursor.value, k);
      if (v != null && v !== this.skipValue) {
        return v;
      } else {
        cursor = cursor.next;
      }
    }
    return null;
  }

  merge(ys: CrDataMap) {
    return this.mergeSkip(ys, fakeUniqueSymbol);
  }
  mergeSkip(ys: CrDataMap, v: CrDataValue) {
    if (!(ys instanceof CrDataMap)) {
      throw new Error("Expected map");
    }

    let result = new CrDataMap(null);
    result.skipValue = v;
    ys.turnSingleMap();
    result.chain = {
      value: ys.chain.value,
      next: this.chain,
    };
    result.depth = this.depth + 1;
    if (result.depth > 5) {
      // 5 by experience, limit to squash linked list to value
      result.turnSingleMap();
    }
    return result;
  }
 // more code...

```

The trick for List made significant performance boost, especially for arguments spreading in JavaScript. The benefit of the trick in `merge` is unclear. Accessing values of Maps is slower in this way, so this might not be a good solution.

There are also some other tricks(even dirty ones) for hash function to reduce the cost of persistent map. However I’m understanding hashing and RRB tree well and so I still need to investigate into that.

### Other thoughts…

As I said my main purpose is to run calcit-editor created programs without compiling to another language first, meanwhile, to use tools from JavaScript ecosystem. At least calcit-js accomplished these goals now, despite of its lack of features comparing to cljs.

A milestone of the project is I migrated Respo, which is my own virtual DOM libray, into calcit-js. I tried, the calcit-js one is roughly 5x slower compare to the optimized cljs one, it’s not very significant slowness:

- Respo in cljs [Respo](http://repo.respo-mvc.org/respo/)
- Respo in calcit-js [Respo](http://repo.respo-mvc.org/respo.calcit/)

My gains are more familiar tools from JavaScript side and faster boot time. Debugging the code generated from calcit-js is easier than from cljs since it has similar semantics with JavaScript, but meanwhile, it lacks of source maps. Being an experiment, I think it’s good enough.

For modules system, it uses a local folder `~/.config/calcit/modules` to locate a different package. Very poor solution, no version resolutions.

It actually recompiles and then to know where a js file is changed(macros involved everywhere, hum?). So if one day my project gets large, it would probably be slower than cljs.

I’m not into C programming so I have difficulties porting TCP or other servers. That made the project rather limited, just no C extension to use. Also no VMs are well. Might be lots of considerations in future.

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [February 7, 2021, 3:53pm UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/2 "2021-02-07T15:53:13Z")

</div>

In the past weeks I also migrated my own projects:

- Respo: virtual DOM library [GitHub - Respo/respo.calcit: A virtual DOM library in calcit-js · GitHub](https://github.com/Respo/respo.calcit)
- Phlox: “virtual DOM”-like wrapper around PIXI.js [GitHub - Phlox-GL/phlox: Calcit-js declarative wrapper on PIXI.js · GitHub](https://github.com/Quamolit/phlox.calcit)

to calcit-js, and luckily they both work quite well.(Not use in larger apps though…)

Also I added `cr --emit-ir` option to let calcit-runner to emit a [`program-ir.json`](https://gist.github.com/jiyinyiyong/0e39f46c10657449b05b18876af5d3cf) file, which is the program data after macro expanding, but before js code emitting. Meanwhile I built a [ir-viewer](https://github.com/calcit-lang/calcit-ir-viewer) page with Respo.calcit, as a debugging tool for the IR. So here you go:

 ![image](https://clojureverse.org/uploads/default/original/2X/b/b915a38ff036f370e6bdad2dfae9ad2fb6bc6eed.png)

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [April 29, 2021, 8:32am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/3 "2021-04-29T08:32:09Z")

</div>

Updates on calcit-js:

The interpreter/codegen has been rewritten in Rust for the power of ADTs. Performance is roughly same.

> **[calcit-lang/calcit\_runner.rs](https://github.com/calcit-lang/calcit_runner.rs)**
>
> Calcit Runner with Rust. Contribute to calcit-lang/calcit\_runner.rs development by creating an account on GitHub.

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [May 22, 2021, 6:58pm UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/4 "2021-05-22T18:58:06Z")

</div>

Some feedbacks after using Calcit-js for some months…

### Advantages

Just imaging you are writing ClojureScript, 1k+ lines of code, compiled(cold startup) with \<500ms. Calcit-js has fewer features and so a lot faster.

The compiled code are just modern JavaScript with `import / export` syntax, so that works with Webpack and Vite quite well, no worries. Just as planned.

In Calcit-js I write `map ([] 1 2 3) inc` rather than `(map inc [1 2 3])`, I found it quite easy to connect that in code `-> xs (map inc) (nth 1)` which Clojure would need `as->` to do. Small change but looks not bad.

### Downsides

Somehow I do miss features from shadow-cljs, compiling multiple targets in a single run… watching inlined assets, better warnings.. and Calcit-js is rough and even kind like broken in such aspects. And I have to start both calcit-js compiler and Webpack(or Vite) in order to run the page. shadow-cljs is really simpler. And I also copied `:require (... :default ...)` from shadow-cljs to make some imports works.

ClojureScript’s hot code swapping is nice. When I switch back to Webpack and Vite for hot code swapping, function definitions are inside closures, not as easily replaced as in ClojureScript. That make me flurried for a while and at last I have to fix that with extra `atom`s, a dirty way. Plus that webpack HMR for Node.js side is so rough, never becoming as simple as shadow-cljs.

I implemented macros in Calcit-js so it generates core functions like in Clojure. Well, I only have very limited experience on this and turned out the macro system was quite unrobust. I fixed several bugs and it seemed to work. But it’s probably still very far from the power of Clojure macro.

For easiness of development, I only copied features I wanted mainly for using it in JavaScript environments. One great feature I didn’t try to copy was “multiple dispatch”, or “protocol”, something like that, features for polymorphism. As I used Rust to built the interpreter/codegen, I become more and more realized about the power of polymorphism, or “ad-hoc polymorphism” in this case.(for parametric polymorphism I can still use tagged unions to simulate). To be honest I don’t have a clear sight on how to implement ad-hoc polymorphism in Calcit-js being a dynamic typed language. But now I feel more and more that I would need it.

### and…

still trying to make it better. and it’s fun to extend the tools itself for more abilities.

---

<div class="post-metadata">

### Author: ![didibus](https://clojureverse.org/user_avatar/clojureverse.org/didibus/32/951_2.png) [@didibus](https://clojureverse.org/u/didibus)
#### Post date: [May 22, 2021, 7:15pm UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/5 "2021-05-22T19:15:11Z")

</div>

> [@jiyinyiyong](#):
>
> I found it quite easy to connect that in code `-> xs (map inc) (nth 1)` which Clojure would need `as->` to do. Small change but looks not bad

```auto
(->> [1 2 3] (map inc) (take 1))

```

All sequence functions take the sequence as the last argument, so if you keep to those you can thread them all with thread-last.

Otherwise I think it’s good you’re experimenting on your own little programming language, you’ve probably learned a lot along the way.

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [May 23, 2021, 3:26am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/6 "2021-05-23T03:26:03Z")

</div>

> [@didibus](#):
>
> All sequence functions take the sequence as the last argument, so if you keep to those you can thread them all with thread-last.

I mean in Calcit-js, all sequence functions take the sequence as the FIRST argument. Since may functions for operating collections, `get` `nth` `assoc`… take collections as first argument, now I can chain them together with a single `->`.

---

<div class="post-metadata">

### Author: ![didibus](https://clojureverse.org/user_avatar/clojureverse.org/didibus/32/951_2.png) [@didibus](https://clojureverse.org/u/didibus)
#### Post date: [May 24, 2021, 12:00am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/7 "2021-05-24T00:00:28Z")

</div>

Ya, I remember there being a reason why in Clojure the sequence functions take it last, where as collection functions take it first, but I don’t recall.

Edit: Found this: [Rules of thumb for function arguments ordering in Clojure - Stack Overflow](https://stackoverflow.com/questions/50275513/rules-of-thumb-for-function-arguments-ordering-in-clojure)

---

<div class="post-metadata">

### Author: ![mjmeintjes](https://clojureverse.org/user_avatar/clojureverse.org/mjmeintjes/32/860_2.png) [@mjmeintjes](https://clojureverse.org/u/mjmeintjes)
#### Post date: [May 24, 2021, 12:04am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/8 "2021-05-24T00:04:18Z")

</div>

[https://clojure.org/guides/faq#arg\_order](https://clojure.org/guides/faq#arg_order)

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [May 24, 2021, 3:37am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/9 "2021-05-24T03:37:14Z")

</div>

that would be more obvious reasons to put sequence at last in Haskell,

> <https://stackoverflow.com/questions/31736467/is-there-significance-in-the-order-of-haskell-function-parameters>

since functions are all curried, it’s a very natural pattern to use partial evaluation and reuse the arguments before the last one as a partial function, then partial functions can be composed:

```auto
myfunc2 = dropEvery 3 . map (`div` 2) . filter even

```

and they even use this pattern for [`lookup`](https://hackage.haskell.org/package/containers-0.4.0.0/docs/Data-Map.html) (our `get` in Clojure):

```auto
lookup :: Ord k => k -> Map k a -> Maybe a

```

And I thought, we are not curried in most functions. And we are less often using that `.` compose. So I prefer always using macros to compose.

Transducers may need that kind of composition over functions, but transducers are rarely used in my projects. Plus we always have macros to save us from the worst cases.

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [June 13, 2021, 2:04pm UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/10 "2021-06-13T14:04:35Z")

</div>

after another day of work… might be boring to read but here are 2 examples of code generated by calcit-js(after running Prettier).

before Prettier, the indentations layout was very ugly.

```auto
import * as $calcit from "./calcit.core";

import { _LT__GT_ } from "./respo.core";

import { _EQ__LT_ } from "./respo.comp.space";

import { _GT__GT_ } from "./respo.core";

import { comp_inspect } from "./respo.comp.inspect";

import { comp_task } from "./respo.app.comp.task";

import { comp_wrap } from "./respo.app.comp.wrap";

import { comp_zero } from "./respo.app.comp.zero";

import { div } from "./respo.core";

import { extract_effects_list } from "./respo.core";

import { hsl } from "./respo.util.format";

import { input } from "./respo.core";

import { list__GT_ } from "./respo.core";

import { memof_call } from "./memof.alias";

import * as $respo_DOT_app_DOT_style_DOT_widget from "./respo.app.style.widget";

import * as $respo_DOT_schema from "./respo.schema";

import { span } from "./respo.core";

import { text_width } from "./respo.util.dom";

import { time_BANG_ } from "./respo.util.dom";

export function effect_focus() {
  if (arguments.length !== 0) throw new Error("argument sizes do not match");
  return $calcit._AND __PCT__ MAP_($respo_DOT_schema.Effect, $calcit.kwd("name"), $calcit.kwd("effect-focus"), $calcit.kwd("coord"), $calcit._LIST_(), $calcit.kwd("args"), $calcit._LIST_(), $calcit.kwd("method"), function f_PCT_(args __74, params__ 75) {
    if (arguments.length !== 2) throw new Error("argument sizes do not match");
    let v __76 = args__ 74;
    let v __77 = params__ 75;
    let action = $calcit._AND_list_COL_nth(v__77, 0);
    let parent = $calcit._AND_list_COL_nth(v__77, 1);
    let at_place_QUES_ = $calcit._AND_list_COL_nth(v__77, 2);
    return console.log("todolist effect:", action);
  });
}
export function number_order(a, b) {
  if (arguments.length !== 2) throw new Error("argument sizes do not match");

  if ($calcit._AND__LT_(a, b)) {
    return -1;
  } else if ($calcit._AND__GT_(a, b)) {
    return 1;
  } else {
    return 0;
  }
}
export function on_focus(e, dispatch_BANG_) {
  if (arguments.length !== 2) throw new Error("argument sizes do not match");
  console.log($calcit.printable("Just focused~"));
}
export function run_test_BANG_(dispatch_BANG_, acc) {
  if (arguments.length !== 2) throw new Error("argument sizes do not match");
  let started = time_BANG_();
  dispatch_BANG_($calcit.kwd("clear"), null);
  $calcit.apply(
    function generated_loop(x) {
      if (arguments.length !== 1) throw new Error("argument sizes do not match");

      let ret_AUTO_2 = null;
      let times_AUTO_3 = 0;
      while (true) {
        /* Tail Recursion */
        if (times_AUTO_3 > 10000) throw new Error("tail recursion not stopping");
        dispatch_BANG_($calcit.kwd("add"), "empty");

        if ($calcit._GT_(x, 0)) {
          ret_AUTO_2 = $calcit.recur($calcit.dec(x));
        }
        ret_AUTO_2 = null;

        if (ret_AUTO_2 instanceof $calcit.CalcitRecur) {
          if (ret_AUTO_2.args.length !== 1) throw new Error("argument sizes do not match");
          [x] = ret_AUTO_2.args;

          times_AUTO_3 += 1;
          continue;
        } else {
          return ret_AUTO_2;
        }
      }
    },

    $calcit._LIST_(20)
  );
  $calcit.apply(
    function generated_loop(x) {
      if (arguments.length !== 1) throw new Error("argument sizes do not match");

      let ret_AUTO_5 = null;
      let times_AUTO_6 = 0;
      while (true) {
        /* Tail Recursion */
        if (times_AUTO_6 > 10000) throw new Error("tail recursion not stopping");
        dispatch_BANG_($calcit.kwd("hit-first"), $calcit.rand());

        if ($calcit._GT_(x, 0)) {
          ret_AUTO_5 = $calcit.recur($calcit.dec(x));
        }
        ret_AUTO_5 = null;

        if (ret_AUTO_5 instanceof $calcit.CalcitRecur) {
          if (ret_AUTO_5.args.length !== 1) throw new Error("argument sizes do not match");
          [x] = ret_AUTO_5.args;

          times_AUTO_6 += 1;
          continue;
        } else {
          return ret_AUTO_5;
        }
      }
    },

    $calcit._LIST_(20)
  );
  dispatch_BANG_($calcit.kwd("clear"), null);
  $calcit.apply(
    function generated_loop(x) {
      if (arguments.length !== 1) throw new Error("argument sizes do not match");

      let ret_AUTO_8 = null;
      let times_AUTO_9 = 0;
      while (true) {
        /* Tail Recursion */
        if (times_AUTO_9 > 10000) throw new Error("tail recursion not stopping");
        dispatch_BANG_($calcit.kwd("add"), "only 10 items");

        if ($calcit._GT_(x, 0)) {
          ret_AUTO_8 = $calcit.recur($calcit.dec(x));
        }
        ret_AUTO_8 = null;

        if (ret_AUTO_8 instanceof $calcit.CalcitRecur) {
          if (ret_AUTO_8.args.length !== 1) throw new Error("argument sizes do not match");
          [x] = ret_AUTO_8.args;

          times_AUTO_9 += 1;
          continue;
        } else {
          return ret_AUTO_8;
        }
      }
    },

    $calcit._LIST_(10)
  );
  let cost = $calcit._SUB_(time_BANG_(), started);

  if ($calcit._LT_($calcit.count(acc), 40)) {
    return setTimeout(function f_PCT_() {
      if (arguments.length !== 0) throw new Error("argument sizes do not match");
      return run_test_BANG_(dispatch_BANG_, $calcit.conj(acc, cost));
    }, 0);
  } else {
    console.log($calcit.printable("result:", $calcit.sort(acc, number_order)));
  }
}
export function on_test(e, dispatch_BANG_) {
  if (arguments.length !== 2) throw new Error("argument sizes do not match");
  console.log($calcit.printable("trigger test!"));
  return run_test_BANG_(dispatch_BANG_, $calcit._LIST_());
}
export function comp_todolist(states, tasks) {
  if (arguments.length !== 2) throw new Error("argument sizes do not match");
  return extract_effects_list(
    $calcit._AND __PCT__ MAP_(
      $respo_DOT_schema.Component,
      $calcit.kwd("effects"),
      $calcit._LIST_(),
      $calcit.kwd("name"),
      $calcit.kwd("comp-todolist"),
      $calcit.kwd("tree"),
      (function __fn__ () {
        let cursor = (function __fn__ () {
          let v1__72 = $calcit.get(states, $calcit.kwd("cursor"));

          if ($calcit.nil_QUES_(v1__72)) {
            return $calcit._LIST_();
          } else if ($calcit._EQ_(false, v1__72)) {
            return $calcit._LIST_();
          } else {
            return v1__72;
          }
        })();
        let state = (function __fn__ () {
          let v1__73 = $calcit.get(states, $calcit.kwd("data"));

          if ($calcit.nil_QUES_(v1__73)) {
            return initial_state;
          } else if ($calcit._EQ_(false, v1__73)) {
            return initial_state;
          } else {
            return v1__73;
          }
        })();
        return $calcit._LIST_(
          effect_focus(),
          div(
            $calcit._AND__MAP_($calcit.kwd("style"), style_root),
            comp_inspect("States", state, $calcit._AND__MAP_($calcit.kwd("left"), "80px")),
            div(
              $calcit._AND__MAP_($calcit.kwd("style"), style_panel),
              input(
                $calcit._AND__MAP_(
                  $calcit.kwd("placeholder"),
                  "Text",
                  $calcit.kwd("value"),
                  $calcit.get(state, $calcit.kwd("draft")),
                  $calcit.kwd("style"),
                  $calcit.merge($respo_DOT_app_DOT_style_DOT_widget.input, $calcit._AND__MAP_($calcit.kwd("width"), $calcit._AND_max(200, $calcit._ADD_(24, text_width($calcit.get(state, $calcit.kwd("draft")), 16, "BlinkMacSystemFont"))))),
                  $calcit.kwd("on-input"),
                  function f_PCT_(e, d_BANG_) {
                    if (arguments.length !== 2) throw new Error("argument sizes do not match");
                    return d_BANG_(cursor, $calcit.assoc(state, $calcit.kwd("draft"), $calcit.get(e, $calcit.kwd("value"))));
                  },
                  $calcit.kwd("on-focus"),
                  on_focus
                )
              ),
              _EQ__LT_(8, null),
              span(
                $calcit._AND__MAP_($calcit.kwd("style"), $respo_DOT_app_DOT_style_DOT_widget.button, $calcit.kwd("on-click"), function f_PCT_(e, d_BANG_) {
                  if (arguments.length !== 2) throw new Error("argument sizes do not match");
                  d_BANG_($calcit.kwd("add"), $calcit.get(state, $calcit.kwd("draft")));
                  return d_BANG_(cursor, $calcit.assoc(state, $calcit.kwd("draft"), ""));
                }),
                span($calcit._AND__MAP_($calcit.kwd("on-click"), null, $calcit.kwd("inner-text"), "Add"))
              ),
              _EQ__LT_(8, null),
              span(
                $calcit._AND__MAP_($calcit.kwd("inner-text"), "Clear", $calcit.kwd("style"), $respo_DOT_app_DOT_style_DOT_widget.button, $calcit.kwd("on-click"), function f_PCT_(e, d_BANG_) {
                  if (arguments.length !== 2) throw new Error("argument sizes do not match");
                  return d_BANG_($calcit.kwd("clear"), null);
                })
              ),
              _EQ__LT_(8, null),
              div($calcit._AND__MAP_(), div($calcit._AND__MAP_($calcit.kwd("style"), $respo_DOT_app_DOT_style_DOT_widget.button, $calcit.kwd("on-click"), on_test), _LT__GT_("heavy tasks")))
            ),
            list__GT_(
              $calcit._AND__MAP_($calcit.kwd("class-name"), "task-list", $calcit.kwd("style"), style_list),
              $calcit.map(
                $calcit.reverse(
                  (function __fn__ () {
                    let v1__80 = tasks;

                    if ($calcit.nil_QUES_(v1__80)) {
                      return $calcit._LIST_();
                    } else if ($calcit._EQ_(false, v1__80)) {
                      return $calcit._LIST_();
                    } else {
                      return v1__80;
                    }
                  })()
                ),
                function f_PCT_(task) {
                  if (arguments.length !== 1) throw new Error("argument sizes do not match");
                  let task_id = $calcit.get(task, $calcit.kwd("id"));
                  return $calcit._LIST_(task_id, memof_call(comp_task, _GT__GT_(states, task_id), task));
                }
              )
            ),
            $calcit._GT_($calcit.count(tasks), 0)
              ? div(
                  $calcit._AND__MAP_($calcit.kwd("spell-check"), true, $calcit.kwd("style"), style_toolbar),
                  div(
                    $calcit._AND__MAP_(
                      $calcit.kwd("style"),
                      $respo_DOT_app_DOT_style_DOT_widget.button,
                      $calcit.kwd("on-click"),
                      $calcit.not($calcit.get(state, $calcit.kwd("locked?")))
                        ? function f_PCT_(e, d_BANG_) {
                            if (arguments.length !== 2) throw new Error("argument sizes do not match");
                            return d_BANG_($calcit.kwd("clear"), null);
                          }
                        : null
                    ),
                    _LT__GT_("Clear2")
                  ),
                  _EQ__LT_(8, null),
                  div(
                    $calcit._AND__MAP_($calcit.kwd("style"), $respo_DOT_app_DOT_style_DOT_widget.button, $calcit.kwd("on-click"), function f_PCT_(e, d_BANG_) {
                      if (arguments.length !== 2) throw new Error("argument sizes do not match");
                      return d_BANG_(cursor, $calcit.update(state, $calcit.kwd("locked?"), $calcit.not));
                    }),
                    _LT__GT_($calcit._AND_str_concat("Lock?", $calcit._AND_str($calcit.get(state, $calcit.kwd("locked?")))))
                  ),
                  _EQ__LT_(8, null),
                  comp_wrap(comp_zero())
                )
              : null,
            comp_inspect("Tasks", tasks, $calcit._AND__MAP_($calcit.kwd("left"), 500, $calcit.kwd("top"), 20))
          )
        );
      })()
    )
  );
}

export var initial_state = $calcit._AND__MAP_($calcit.kwd("draft"), "", $calcit.kwd("locked?"), false);

export var style_list = $calcit._AND__MAP_($calcit.kwd("color"), $calcit.kwd("black"), $calcit.kwd("background-color"), hsl(120, 20, 98));

export var style_panel = $calcit._AND__MAP_($calcit.kwd("display"), $calcit.kwd("flex"), $calcit.kwd("margin-bottom"), 4);

export var style_root = $calcit._AND__MAP_($calcit.kwd("color"), $calcit.kwd("black"), $calcit.kwd("background-color"), hsl(120, 20, 98), $calcit.kwd("line-height"), "24px", "font-size", 16, $calcit.kwd("padding"), 10, $calcit.kwd("font-family"), '"微软雅黑", Verdana');

export var style_toolbar = $calcit._AND__MAP_($calcit.kwd("display"), $calcit.kwd("flex"), $calcit.kwd("flex-direction"), $calcit.kwd("row"), $calcit.kwd("justify-content"), $calcit.kwd("start"), $calcit.kwd("padding"), "4px 0", $calcit.kwd("white-space"), $calcit.kwd("nowrap"));

```

```auto
import * as $calcit from "./calcit.core";

import { collect_mounting } from "./respo.render.effect";

import { collect_unmounting } from "./respo.render.effect";

import { collect_updating } from "./respo.render.effect";

import { compare_xy } from "./respo.util.comparator";

import { component_QUES_ } from "./respo.util.detect";

import { element_QUES_ } from "./respo.util.detect";

import * as $respo_DOT_schema_DOT_op from "./respo.schema.op";

import { val_of_first } from "./respo.util.list";

export let find_props_diffs = function find_props_diffs(collect_BANG_, coord, n_coord, old_props, new_props) {
  if (arguments.length !== 5) throw new Error("argument sizes do not match");

  let ret_AUTO_2 = null;
  let times_AUTO_3 = 0;
  while (true) {
    /* Tail Recursion */
    if (times_AUTO_3 > 10000) throw new Error("tail recursion not stopping");
    let was_empty_QUES_ = $calcit.empty_QUES_(old_props);
    let now_empty_QUES_ = $calcit.empty_QUES_(new_props);

    if (was_empty_QUES_ ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      ret_AUTO_2 = null;
    } else if (was_empty_QUES_ ? ($calcit.not(now_empty_QUES_) ? $calcit.not(now_empty_QUES_) : false) : false) {
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_prop, coord, n_coord, $calcit.first(new_props)));
      ret_AUTO_2 = $calcit.recur(collect_BANG_, coord, n_coord, old_props, $calcit.rest(new_props));
    } else if ($calcit.not(was_empty_QUES_) ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_prop, coord, n_coord, $calcit.first($calcit.first(old_props))));
      ret_AUTO_2 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.rest(old_props), new_props);
    } else if (true) {
      let old_pair = $calcit.first(old_props);
      let new_pair = $calcit.first(new_props);
      let old_k = $calcit.first(old_pair);
      let old_v = $calcit.last(old_pair);
      let new_k = $calcit.first(new_pair);
      let new_v = $calcit.last(new_pair);
      let old_follows = $calcit.rest(old_props);
      let new_follows = $calcit.rest(new_props);
      let v__54 = compare_xy(old_k, new_k);

      if ($calcit._AND__EQ_(v__54, -1)) {
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_prop, coord, n_coord, old_k));
        ret_AUTO_2 = $calcit.recur(collect_BANG_, coord, n_coord, old_follows, new_props);
      } else if ($calcit._AND__EQ_(v__54, 1)) {
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_prop, coord, n_coord, new_pair));
        ret_AUTO_2 = $calcit.recur(collect_BANG_, coord, n_coord, old_props, new_follows);
      } else if ($calcit._AND__EQ_(v__54, 0)) {
        $calcit._SLSH__EQ_(old_v, new_v) ? collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.replace_prop, coord, n_coord, new_pair)) : null;
        ret_AUTO_2 = $calcit.recur(collect_BANG_, coord, n_coord, old_follows, new_follows);
      } else {
        ret_AUTO_2 = null;
      }
    } else {
      ret_AUTO_2 = null;
    }

    if (ret_AUTO_2 instanceof $calcit.CalcitRecur) {
      if (ret_AUTO_2.args.length !== 5) throw new Error("argument sizes do not match");
      [collect_BANG_, coord, n_coord, old_props, new_props] = ret_AUTO_2.args;

      times_AUTO_3 += 1;
      continue;
    } else {
      return ret_AUTO_2;
    }
  }
};

export let find_style_diffs = function find_style_diffs(collect_BANG_, c_coord, coord, old_style, new_style) {
  if (arguments.length !== 5) throw new Error("argument sizes do not match");

  let ret_AUTO_5 = null;
  let times_AUTO_6 = 0;
  while (true) {
    /* Tail Recursion */
    if (times_AUTO_6 > 10000) throw new Error("tail recursion not stopping");
    let was_empty_QUES_ = $calcit.empty_QUES_(old_style);
    let now_empty_QUES_ = $calcit.empty_QUES_(new_style);

    if ($calcit.identical_QUES_(old_style, new_style)) {
      ret_AUTO_5 = null;
    } else if (was_empty_QUES_ ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      ret_AUTO_5 = null;
    } else if (was_empty_QUES_ ? ($calcit.not(now_empty_QUES_) ? $calcit.not(now_empty_QUES_) : false) : false) {
      let entry = $calcit.first(new_style);
      let follows = $calcit.rest(new_style);
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_style, c_coord, coord, entry));
      ret_AUTO_5 = $calcit.recur(collect_BANG_, c_coord, coord, old_style, follows);
    } else if ($calcit.not(was_empty_QUES_) ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      let entry = $calcit.first(old_style);
      let follows = $calcit.rest(old_style);
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_style, c_coord, coord, $calcit.first(entry)));
      ret_AUTO_5 = $calcit.recur(collect_BANG_, c_coord, coord, follows, new_style);
    } else if (true) {
      let old_entry = $calcit.first(old_style);
      let new_entry = $calcit.first(new_style);
      let old_follows = $calcit.rest(old_style);
      let new_follows = $calcit.rest(new_style);
      let v__55 = compare_xy($calcit.first(old_entry), $calcit.first(new_entry));

      if ($calcit._AND__EQ_(v__55, -1)) {
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_style, c_coord, coord, $calcit.first(old_entry)));
        ret_AUTO_5 = $calcit.recur(collect_BANG_, c_coord, coord, old_follows, new_style);
      } else if ($calcit._AND__EQ_(v__55, 1)) {
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_style, c_coord, coord, new_entry));
        ret_AUTO_5 = $calcit.recur(collect_BANG_, c_coord, coord, old_style, new_follows);
      } else if ($calcit._AND__EQ_(v__55, 0)) {
        $calcit.not($calcit.identical_QUES_($calcit.last(old_entry), $calcit.last(new_entry))) ? collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.replace_style, c_coord, coord, new_entry)) : null;
        ret_AUTO_5 = $calcit.recur(collect_BANG_, c_coord, coord, old_follows, new_follows);
      } else {
        ret_AUTO_5 = null;
      }
    } else {
      ret_AUTO_5 = null;
    }

    if (ret_AUTO_5 instanceof $calcit.CalcitRecur) {
      if (ret_AUTO_5.args.length !== 5) throw new Error("argument sizes do not match");
      [collect_BANG_, c_coord, coord, old_style, new_style] = ret_AUTO_5.args;

      times_AUTO_6 += 1;
      continue;
    } else {
      return ret_AUTO_5;
    }
  }
};

export let find_element_diffs = function find_element_diffs(collect_BANG_, coord, n_coord, old_tree, new_tree) {
  if (arguments.length !== 5) throw new Error("argument sizes do not match");

  let ret_AUTO_8 = null;
  let times_AUTO_9 = 0;
  while (true) {
    /* Tail Recursion */
    if (times_AUTO_9 > 10000) throw new Error("tail recursion not stopping");

    if ($calcit.identical_QUES_(old_tree, new_tree)) {
      ret_AUTO_8 = null;
    } else if (component_QUES_(old_tree) ? (component_QUES_(new_tree) ? component_QUES_(new_tree) : false) : false) {
      let next_coord = $calcit.conj(coord, $calcit.get(new_tree, $calcit.kwd("name")));

      if ($calcit._EQ_($calcit.get(old_tree, $calcit.kwd("name")), $calcit.get(new_tree, $calcit.kwd("name")))) {
        collect_updating(collect_BANG_, $calcit.kwd("before-update"), coord, n_coord, old_tree, new_tree);
        find_element_diffs(collect_BANG_, next_coord, n_coord, $calcit.get(old_tree, $calcit.kwd("tree")), $calcit.get(new_tree, $calcit.kwd("tree")));
        ret_AUTO_8 = collect_updating(collect_BANG_, $calcit.kwd("update"), coord, n_coord, old_tree, new_tree);
      } else {
        collect_unmounting(collect_BANG_, coord, n_coord, old_tree, true);
        find_element_diffs(collect_BANG_, next_coord, n_coord, $calcit.get(old_tree, $calcit.kwd("tree")), $calcit.get(new_tree, $calcit.kwd("tree")));
        ret_AUTO_8 = collect_mounting(collect_BANG_, coord, n_coord, new_tree, true);
      }
    } else if (component_QUES_(old_tree) ? (element_QUES_(new_tree) ? element_QUES_(new_tree) : false) : false) {
      collect_unmounting(collect_BANG_, $calcit.conj(coord, $calcit.get(old_tree, $calcit.kwd("name"))), n_coord, old_tree, true);
      ret_AUTO_8 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.get(old_tree, $calcit.kwd("tree")), new_tree);
    } else if (element_QUES_(old_tree) ? (component_QUES_(new_tree) ? component_QUES_(new_tree) : false) : false) {
      let new_coord = $calcit.conj(coord, $calcit.get(new_tree, $calcit.kwd("name")));
      find_element_diffs(collect_BANG_, new_coord, n_coord, old_tree, $calcit.get(new_tree, $calcit.kwd("tree")));
      ret_AUTO_8 = collect_mounting(collect_BANG_, coord, n_coord, new_tree, true);
    } else if (element_QUES_(old_tree) ? (element_QUES_(new_tree) ? element_QUES_(new_tree) : false) : false) {
      if ($calcit._SLSH__EQ_($calcit.get(old_tree, $calcit.kwd("name")), $calcit.get(new_tree, $calcit.kwd("name")))) {
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.replace_element, coord, n_coord, new_tree));
        ret_AUTO_8 = null;
      } else {
        find_props_diffs(collect_BANG_, coord, n_coord, $calcit.get(old_tree, $calcit.kwd("attrs")), $calcit.get(new_tree, $calcit.kwd("attrs")));
        (function __fn__ () {
          let old_style = $calcit.get(old_tree, $calcit.kwd("style"));
          let new_style = $calcit.get(new_tree, $calcit.kwd("style"));

          if ($calcit._SLSH__EQ_(old_style, new_style)) {
            return find_style_diffs(collect_BANG_, coord, n_coord, old_style, new_style);
          }
          return null;
        })();
        (function __fn__ () {
          let old_events = $calcit.keys_non_nil(
            (function __fn__ () {
              let v1__56 = $calcit.get(old_tree, $calcit.kwd("event"));

              if ($calcit.nil_QUES_(v1__56)) {
                return $calcit._AND__MAP_();
              } else if ($calcit._EQ_(false, v1__56)) {
                return $calcit._AND__MAP_();
              } else {
                return v1__56;
              }
            })()
          );
          let new_events = $calcit.keys_non_nil(
            (function __fn__ () {
              let v1__57 = $calcit.get(new_tree, $calcit.kwd("event"));

              if ($calcit.nil_QUES_(v1__57)) {
                return $calcit._AND__MAP_();
              } else if ($calcit._EQ_(false, v1__57)) {
                return $calcit._AND__MAP_();
              } else {
                return v1__57;
              }
            })()
          );

          if ($calcit._SLSH__EQ_(old_events, new_events)) {
            let added_events = $calcit.difference(new_events, old_events);
            let removed_events = $calcit.difference(old_events, new_events);
            $calcit.foldl(added_events, null, function doseq_fn_PCT_(_acc, event_name) {
              if (arguments.length !== 2) throw new Error("argument sizes do not match");
              return collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.set_event, coord, n_coord, event_name));
            });
            return $calcit.foldl(removed_events, null, function doseq_fn_PCT_(_acc, event_name) {
              if (arguments.length !== 2) throw new Error("argument sizes do not match");
              return collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_event, coord, n_coord, event_name));
            });
          }
          return null;
        })();
        let old_children = $calcit.get(old_tree, $calcit.kwd("children"));
        let new_children = $calcit.get(new_tree, $calcit.kwd("children"));
        ret_AUTO_8 = find_children_diffs(collect_BANG_, coord, n_coord, 0, old_children, new_children);
      }
    } else if (true) {
      ret_AUTO_8 = console.warn("Diffing unknown params", old_tree, new_tree);
    } else {
      ret_AUTO_8 = null;
    }

    if (ret_AUTO_8 instanceof $calcit.CalcitRecur) {
      if (ret_AUTO_8.args.length !== 5) throw new Error("argument sizes do not match");
      [collect_BANG_, coord, n_coord, old_tree, new_tree] = ret_AUTO_8.args;

      times_AUTO_9 += 1;
      continue;
    } else {
      return ret_AUTO_8;
    }
  }
};

export let find_children_diffs = function find_children_diffs(collect_BANG_, coord, n_coord, index, old_children, new_children) {
  if (arguments.length !== 6) throw new Error("argument sizes do not match");

  let ret_AUTO_11 = null;
  let times_AUTO_12 = 0;
  while (true) {
    /* Tail Recursion */
    if (times_AUTO_12 > 10000) throw new Error("tail recursion not stopping");
    let was_empty_QUES_ = $calcit.empty_QUES_(old_children);
    let now_empty_QUES_ = $calcit.empty_QUES_(new_children);

    if (was_empty_QUES_ ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      ret_AUTO_11 = null;
    } else if (was_empty_QUES_ ? ($calcit.not(now_empty_QUES_) ? $calcit.not(now_empty_QUES_) : false) : false) {
      let pair = $calcit.first(new_children);
      let k = $calcit.first(pair);
      let element = $calcit.last(pair);
      let new_coord = $calcit.conj(coord, k);
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.append_element, new_coord, n_coord, element));
      collect_mounting(collect_BANG_, coord, $calcit.conj(n_coord, index), element, true);
      ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.inc(index), $calcit._LIST_(), $calcit.rest(new_children));
    } else if ($calcit.not(was_empty_QUES_) ? (now_empty_QUES_ ? now_empty_QUES_ : false) : false) {
      let pair = $calcit.first(old_children);
      let k = $calcit.first(pair);
      let new_coord = $calcit.conj(coord, k);
      let new_n_coord = $calcit.conj(n_coord, index);
      collect_unmounting(collect_BANG_, coord, new_n_coord, $calcit.last(pair), true);
      collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_element, new_coord, new_n_coord, null));
      ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, index, $calcit.rest(old_children), $calcit._LIST_());
    } else if (true) {
      let old_keys = $calcit.map($calcit.take(old_children, 16), $calcit.first);
      let new_keys = $calcit.map($calcit.take(new_children, 16), $calcit.first);
      let x1 = $calcit.first(old_keys);
      let y1 = $calcit.first(new_keys);
      let match_x1 = function f_PCT_(x) {
        if (arguments.length !== 1) throw new Error("argument sizes do not match");
        return $calcit._EQ_(x, x1);
      };
      let match_y1 = function f_PCT_(x) {
        if (arguments.length !== 1) throw new Error("argument sizes do not match");
        return $calcit._EQ_(x, y1);
      };
      let x1_remains_QUES_ = $calcit.any_QUES_(new_keys, match_x1);
      let y1_existed_QUES_ = $calcit.any_QUES_(old_keys, match_y1);
      let old_follows = $calcit.rest(old_children);
      let new_follows = $calcit.rest(new_children);

      if ($calcit._EQ_(x1, y1)) {
        let old_element = val_of_first(old_children);
        let new_element = val_of_first(new_children);
        find_element_diffs(collect_BANG_, $calcit.conj(coord, x1), $calcit.conj(n_coord, index), old_element, new_element);
        ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.inc(index), old_follows, new_follows);
      } else if (x1_remains_QUES_ ? ($calcit.not(y1_existed_QUES_) ? $calcit.not(y1_existed_QUES_) : false) : false) {
        let pair = $calcit.first(new_children);
        let k = $calcit.first(pair);
        let element = $calcit.last(pair);
        let new_coord = $calcit.conj(coord, k);
        let new_n_coord = $calcit.conj(n_coord, index);
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_element, new_coord, new_n_coord, element));
        collect_mounting(collect_BANG_, coord, new_n_coord, val_of_first(new_children), true);
        ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.inc(index), old_children, new_follows);
      } else if ($calcit.not(x1_remains_QUES_) ? (y1_existed_QUES_ ? y1_existed_QUES_ : false) : false) {
        let pair = $calcit.first(old_children);
        let k = $calcit.first(pair);
        let new_coord = $calcit.conj(coord, k);
        let new_n_coord = $calcit.conj(n_coord, index);
        collect_unmounting(collect_BANG_, coord, new_n_coord, $calcit.last(pair), true);
        collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_element, new_coord, new_n_coord, null));
        ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, index, old_follows, new_children);
      } else if (true) {
        let xi = $calcit.index_of(new_keys, x1);
        let yi = $calcit.index_of(old_keys, y1);
        let first_old_entry = $calcit.first(old_children);
        let first_new_entry = $calcit.first(new_children);
        let new_n_coord = $calcit.conj(n_coord, index);

        if ($calcit._LT__EQ_(xi, yi)) {
          let new_element = val_of_first(new_children);
          let new_coord = $calcit.conj(coord, y1);
          collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.add_element, new_coord, new_n_coord, new_element));
          collect_mounting(collect_BANG_, coord, new_n_coord, new_element, true);
          ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, $calcit.inc(index), old_children, new_follows);
        } else {
          collect_unmounting(collect_BANG_, coord, new_n_coord, val_of_first(old_children), true);
          collect_BANG_($calcit._LIST_($respo_DOT_schema_DOT_op.rm_element, $calcit.conj(coord, x1), new_n_coord, null));
          ret_AUTO_11 = $calcit.recur(collect_BANG_, coord, n_coord, index, old_follows, new_children);
        }
      } else {
        ret_AUTO_11 = null;
      }
    } else {
      ret_AUTO_11 = null;
    }

    if (ret_AUTO_11 instanceof $calcit.CalcitRecur) {
      if (ret_AUTO_11.args.length !== 6) throw new Error("argument sizes do not match");
      [collect_BANG_, coord, n_coord, index, old_children, new_children] = ret_AUTO_11.args;

      times_AUTO_12 += 1;
      continue;
    } else {
      return ret_AUTO_11;
    }
  }
};

```

also to mention that a new CLI [bundle\_calcit](https://github.com/calcit-lang/calcit_runner.rs#bundler) was added weeks back. for people hate using calcit-editor, it’s still okay to write calcit-js, just losing hot code swapping.

---

<div class="post-metadata">

### Author: ![system](https://clojureverse.org/uploads/default/original/2X/5/51079bf9e4b7d9466242c06cf1e43b9f8bd6da14.png) [@system](https://clojureverse.org/u/system)
#### Post date: [December 13, 2021, 2:05am UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/11 "2021-12-13T02:05:24Z")

</div>

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.

---

<div class="post-metadata">

### Author: ![jiyinyiyong](https://clojureverse.org/user_avatar/clojureverse.org/jiyinyiyong/32/85_2.png) [@jiyinyiyong](https://clojureverse.org/u/jiyinyiyong)
#### Post date: [January 11, 2022, 3:43pm UTC](https://clojureverse.org/t/introducing-calcit-js-toy-language-inspired-by-cljs/7097/12 "2022-01-11T15:43:24Z")

</div>

> add page: compare to Clojure:  
> [From Clojure - Calcit Guide](http://repo.calcit-lang.org/guidebook/intro/from-clojure.html)

Calcit is mostly a ClojureScript dialect. So it should also be considered a Clojure dialect.

There are some significant features Calcit is learning from Clojure,

- Runtime persistent data by default, you can only simulate states with `Ref`s.
- Namespaces
- Hygienic macros(although less powerful)
- Higher order functions
- Keywords
- Compiles to JavaScript, interops
- Hot code swapping while code modified, and trigger an `on-reload` function
- HUD for JavaScript errors

Also there are some differences:

| Feature | Calcit | Clojure |
| --- | --- | --- |
| Host Language | Rust, and use `dylib`s for extending | Java/Clojure, import Mavan packages |
| Syntax | Indentations / Syntax Tree Editor | Parentheses |
| Persistent data | unbalanced 2-3 Tree, with tricks from FingerTree | HAMT / RRB-tree |
| Package manager | `git clone` to a folder | Clojars |
| bundle js modules | ES Modules, with ESBuild/Vite | Google Closure Compiler / Webpack |
| operand order | at first | at last |
| Polymorphism | at runtime, slow `(.map ([] 1 2 3) f)` | at compile time, also supports multi-arities |
| REPL | only at command line: `cr --eval "+ 1 2"` | a real REPL |
| `[]` syntax | `[]` is a built-in function | builtin syntax |
| `{}` syntax | `({} (:a b))` is macro, expands to `(&{} :a :b)` | builtin syntax |

also Calcit is a one-person language, it has too few features compared to Clojure.
