squint-cljs / squint Goto Github PK
View Code? Open in Web Editor NEWLight-weight ClojureScript dialect
Home Page: https://squint-cljs.github.io/squint
Light-weight ClojureScript dialect
Home Page: https://squint-cljs.github.io/squint
Implement not-every?
See #22 for implementation details of sequence functions
Thinking about fundamental functions on collections like assoc
, dissoc
, conj
, disj
, it would be nice to have these work on JS objects in an immutable way.
A common idiom in JS is to use copy-on-write. E.g.
let o1 = {a: 1, b: 2, c: 3};
let o2 = {d: 4, ...o1}; // copy all enumerable properties of o1 into o2
assoc
could be a core function that does that
export function assoc(o, ...pairs) {
let o2 = {...o};
// TODO add pairs to new object o2
return o2;
};
alternatively, it could be a special form in the compiler and literally emit the object spread syntax above.
dissoc
can also be done via object spread:
let o = {a: 1, b: 2, c: 3};
{a, ...o2} = o; // o2 is `{b: 2, c:3}`
To avoid shadowing variables that have the same name as the property we're removing, this should probably be a function in core that encapsulates this.
conj could be added via prototype to objects, sets, and arrays. However this is generally frowned upon because a browser could later implement whatever method we add to it. There are ways around this (using Symbol
), however there still could maybe be hazards with this if at some point before we add the method those prototypes are frozen (would this ever happen? I'm not sure).
A general approach would be to dispatch on the prototype object, and fallback to a method on the object:
let object = Object.getPrototypeOf({});
let array = Object.getPrototypeOf([]);
let set = Object.getPrototypeOf(new Set());
export function conj(o, x) {
switch (Object.getPrototypeOf(o)) {
case object: ...
case array: ...
case set: ...
default:
o.conj(x);
}
}
Probably same as conj
but only special casing sets
Implement every
See #22 for implementation details of sequence functions
One complaint I heard in the cherry channel was how clojurescript had fallen behind with interop. Given that, it might make sense to plan for likely js features coming down the road so that interop doesn't fall behind.
One of these features that I think will be important is the proposal for Records & Tuples: https://tc39.es/proposal-record-tuple/tutorial/
I'm not sure we need to do anything right now but keeping it in mind for designing interop in general seems like a smart move to me.
We can probably close this issue right away but if we want to discuss then this is a good place to do it.
More of a discussion than an issue: Should set literals create JS Set
objects?
Implement not-any?
See #22 for implementation details of sequence functions
Soooo pr-str/prn-str shoud just convert to JSON, right? And pr/prn should output to console.log?
str
should emit "" + ...
code.
I think we still need the core str
function when the common (apply str ,,,)
is used
Implement concat
See #22 for implementation details of sequence functions
Should Clojure symbols map to Symbols, some custom type, or strings like keywords?
Implement sort
See #22 for implementation details of sequence functions
Implement filter
, ignoring transducer arity (see #41 for why)
Moving this from slack to an issue.
In Clojure, transducers are created by calling core seq functions without the collection, e.g. (map inc)
, (filter even?)
. While convenient (no new names needed, no new requires needed), it also adds a couple of challenges:
(first (map inc))
(map inc coll)
The first can be caught by better static analysis (clj-kondo) but the second is hard for existing tooling to do.
I proposed two different provocative solutions in slack:
map
filter
with the collection passed in?Initial response to the first was that it would be too unfamiliar to people coming from Clojure(Script), but that the second would be a good one to try.
We add a new namespace, clava.transducers
which would hold the transducer protocol and transducing functions.
Transducers would be objects that satisfy the transformer protocol, allowing them to interop with other libraries like Ramda.
We could build it in JS, however I'm interested in trying to build it in Clava source. This would necessarily force us to build and exercise #21.
Feedback welcome!
What should be the JS name for assoc_BANG_
?
Should we re-export the beautified names in another module?
What about using these functions from a CLJS project, then we don't need the renaming?
Alternative:
People could compile a script like this:
(def assocIn clava.assoc-in)
and then consume that module.
Hmm, it might actually be a feature that we're using the Clojure convention since you can then import these functions in a real CLJS project:
function foo_bar() {
return 10;
}
exports.foo_bar = foo_bar;
(foo/foo-bar)
Implement interleave
.
See #22 for implementation details of sequence functions
Implement sort-by
See #22 for implementation details of sequence functions
Implement remove
, ignoring transducer arity (see #41 for why)
nth
should emit code the same as unchecked-get
:
(nth [1 2 3] 2)
([1, 2, 3][2])
Implement some
See #22 for implementation details of sequence functions
Currently the following fails:
(let [[foo bar] nil]
foo)
This currently emits calls to nth
, which tries to access the indices on the null
value.
I think we can fix this by special casing null
in nth
.
Semi-related to #9
We could support (ns foo (:require [clava.core :refer [equal?]]))
as a synonym for (:require ["clavascript/core.js" :refer [equal?]])
.
equal?
works on nested arrays, objects and sets and falls back to ===
for other types.
When comparing two objects, first it should be determined if they are of the same type using instanceof
(or so) and then we should defer to helper functions which efficiently check the contents of two things of the same type, e.g. arrayEquals
, setEquals
. A quick first check is to check if the length of the two objects are the same, before traversing the object.
Implement drop-last
.
See #22 for implementation details of sequence functions
Implement reverse
See #22 for implementation details of sequence functions
Implement last
See #22 for implementation details of sequence functions
Implement shuffle
See #22 for implementation details of sequence functions
The Clojure "seq" is a key concept in its language design. It provides a common abstraction for working with all kinds of collections where you want to treat them as an ordered sequence of elements.
JS has a similar abstraction called Iterators. Like a collection in Clojure can be "seqable," a collection in JS can be "iterable." Like seqs, iterators can lazily generate each element or be a view on top of an eager collection. The Iterator & Iterable protocols in JS underpin fundamental operations like for...of
, similar to how Clojure seqs underpin map
, filter
et al. There also exist ways to create custom Iterators in JS using generators, just like in Clojure you can construct lazy sequences via lazy-seq
.
Currently, iterators do not have any operations on them except the commonly use for ... of
comprehension. There is currently a stage 2 proposal for adding more helpers like .map
, .filter
, etc: https://github.com/tc39/proposal-iterator-helpers
The major difference between the two abstractions is that iterators are mutable. Consuming an element in the iteration by calling the .next()
method mutates the iterator object in place. This makes passing a reference of an iterator to a function a somewhat dangerous thing, as you can't be sure whether or not it consumes it.
I would propose two things. I am still thinking this through, so feedback welcome:
map
, filter
, etc. should accept anything that is Iterable
and return a concrete array typeSeqs work well in Clojure because collections are immutable, so an immutable view on top of them does not need to worry about the underlying collection changing. In JS we do not have this guarantee, and an immutable view on top of a mutable collection can lead to surprising behavior. Clava also has a general philosophy of "use the platform" rather than bringing over Clojure abstractions. All of this is why I think basing our sequential operators on Iterators makes more sense than porting seqs.
In the above section, I proposed that we take anything that is Iterable but always return an array in map
, filter
, etc. The alternative would be to return an Iterator directly, similar to the TC39 proposal I linked in the background. However, I think this would lead to a lot of confusion when writing application code. Take the following example:
(defn my-todo-app
(let [[todos set-todos] (React/useState [{:name "Task 1" :completed false} {:name "Task 2" :completed false}])
todos-with-id (map-indexed #(assoc %2 :id %1) todos)
todo-count (count todos-with-id)]
[:div
(for [todo todos-with-id]
[:div {:key (:id todo)} (:name todo)])]))
If map-indexed
returns an Iterator, this code is broken: the (count todos-with-id)
will consume the iterator, which will lead to nothing being in the Iterator when React tries to render the divs. Developers will have to be careful in their application code to only use the result of a map
, map-indexed
or filter
once. I think that this is an unreasonable thing to force developers to keep in mind, which is why I think that we should do these operations eagerly and return an array.
For transducers, however, we can own the Iterator as long as it's in the transducer pipeline. So code like:
(into [] (comp (map inc) (filter even?)) (range 10))
(range 10)
could return an Iterable (e.g. an array). The transducer pipeline can pass an iterator to each stage, avoiding the cost of creating an array each time, before finally adding it all to the array passed to into.
Gotta stop here. Will add more thoughts later. Questions and comments welcome!!
It would be nice to have automatic and consistent formatting for js code as we write it.
@corasaurus-hex String interpolation is a different issue: this issue is just about generating more efficient code for (str foo bar)
=> cherry/str(foo,bar)
vs foo + bar
.
I think string interpolation should also be a built-in feature that compiles directly to JS string interpolation. We can support this using a reader template #i "foo ${name}"
. I'll make a different issue for that.
Originally posted by @borkdude in #10 (comment)
Work in progress in jsx-take-two
branch.
$ ./node_cli.js --show -e '{:foo/bar :baz}'
({ "bar": "baz" });
What should (= x y)
compile into?
Implement flatten
.
See #22 for implementation details of sequence functions
Implement but-last
.
See #22 for implementation details of sequence functions
Is this something we want to add? If we do want to add this then we should probably do this before we do pr/prn, right?
Right now, protocols generate a LOT of code. This makes it sort of hard to justify using it for a lot of standard library stuff.
In JS practice, protocol-like extensions are done using Symbol
s that are added to objects as properties. The symbols allow one to guarantee that there will be no conflicts with other code extending it.
The way I think this could work is like the following. For code like:
(defprotocol IFoo
(bar [this])
(baz [this a])
(baz [this a b])
(baz [this a b & more]))
It would generate the following code:
var IFoo = Symbol.for("my.ns.IFoo");
var IFoo_bar = Symbol.for("my.ns.IFoo/bar");
var IFoo_baz = Symbol.for("my.ns.IFoo/baz");
function bar(o) {
assert_method(o, IFoo_bar);
return o[IFoo_bar](o);
}
function baz(o, ...args) {
assert_method(o, IFoo_baz);
return o[IFoo_baz].apply(o, o, args);
}
Extension would mutate the object, adding symbol as a property:
(extend-type MyClass
IFoo
(bar [mc] :bar)
(baz [mc a] [:baz a])
(baz [mc a b] [:baz a b])
(baz [mc a b & more] (into [:baz a b] more))
MyClass[IFoo] = true;
MyClass[IFoo_bar] = function (mc, a) { return ["baz", a]; };
MyClass[IFoo_baz] = function (mc, ...args) {
if (args.length === 1) {
return ["baz", args[0]];
} else if (args.length === 2) {
return ["baz", args[0], args[1]];
} else if (args.length > 2) {
let [a, b, ...more] = args;
return into(["baz", a, b], more);
} else {
throw new Error("Invalid arity: " + args.length);
}
}
Implement repeat
See #22 for implementation details of sequence functions
Currently, clavascript maps are compiled to JS objects. This works fine as long until you need complex keys, e.g.
{[:foo "123"] {:foo/id "123" :bar "baz"}}
In the above case, the vector is used as a key. JS objects only support string keys.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.