- prevent a space between
.
and anything to prevent ambiguity between strict and tolerant mode - (?) replace
=
for==
and$a: query
to$a = query
- (?) replace
+
and-
special meaning to(+)
,(-)
; that's a set[SET MODE]
; may be add(D)
or(%)
(difference),(I)
or(/)
(intersection),(x)
/(*)
(multiplication) – alternative(or)
,(and)
,(xor)
(difference),(mul)
- make unclosed brackets (
)
,]
and}
) not an error in tolerant mode - change
~=
to work with a string on right side - type checking: smth like
expr is array
, orexpr.is('array', 'number')
,.type()
method - type casting: smth like
expr as array
(as number
,as int
,as string
etc), orexpr.as('array')
- new function syntax
-
=>body
- (?)
()=>body
- (?)
($a, $b)=>body
- params in function def (optional), i.e.
($foo) => ...
, by default=> ...
equals to($$) => ...
-
- resolve a ref to a method to invoke i.e.
$(...)()
orfoo.$(...)()
– alternative.call(refToFn, ...args)
- namespaces
- tree() (attempt #1)
-
replace()
(~= s/../../
?) - (?) mapFilter() looks like
nothing
solves the problem - global data and context, related to namespaces
- non-dedup
.map()
,.()
,..()
– important for stat/math -
nothing
! (see below) -
dedup()
– deduplicate similar values/objects in an array;dedup(hashFn?, mergeFn?)
, wherehashFn
(by default=>shapeId()
) a function to compute a value for comparison,mergeFn
(by default=>$
) is a function to merge current and duplicate value (the result will become new current) (experiments) - Filtering subqueries (see below)
-
in object
/not in object
support, like in JS ('foo' in obj
) -
.pick(<smth>)
-
{ $a }
->{ a: $a }
- fix
(a ? b : c) ? d : e
->a ? b : (c ? d : e)
- strings as an object property (i.e. `{ "foo": 1 }`` )
- negative numbers
-
-
/+
as unary operators (-foo
,+bar
,-5
,+"45"
) - prevent a space between
.(
,.[
,..(
by adding such tokens - change
~=
to work with a function on right side - change
.sort()
to take two arg functions as a comparator - pipeline operator (see below)
- variables override in nested scopes, as it happens in JS, i.e.
$a: 123; query.($a: 234; { $a })
will throw no exception and produce{ a: 234 }
. At first glace, variable overriding avoid confusion. However, this limitation is not natural and we able to define the same variables in different nested scope, so no single place of variable definition anyway. It also prevent as to reuse queries by copy&paste parts from a query to another one - a block inside a parenthesis instead of expression
-
(?) definitions as a path i.e.->.$foo
orfoo.bar.$baz
.[$foo]
? -
(?) parenthesis invocation, i.e.(looks like a bad idea)($fnA or $fnB)(...)
- sort function and function list, i.e.
foo asc
andfoo desc, bar asc
– both returns a single function (like(a, b) => { a = query(a); b = query(b); return a < b || -(a > b) }
) -
split()
method -
join()
method -
slice()
method - slice notation (PR)
- definitions as method invocation i.e.
$foo()
orfoo.bar.$baz()
-
[...expr]
syntax -
query[expression]
is index accessor for arrays, i.e.ar[2]
,ar[-3]
;ar['foo bar']
will return nothing, it should be converted toar.($['foo bar'])
- string template (draft)
-
reduce()
(see below) - make the lowest priority for
asc
&desc
, e.g. infoo + bar desc
onlybar desc
converts into function -
indexOf()
/lastIndexOf()
Suppose we need to fetch some kind object that contains some attribute deep inside, we may perform smth like that:
sites.[stylesheets.errors.[name="ParseError"]]
Such query will return a list of sites. However it's not so useful since sites will go as is, but obvious we need to know which stylesheets for those sites has specific errors. To achieve this, our query should looks like:
sites.({
...,
stylesheets: stylesheets.({
...,
errors: errors.[name="ParseError"]
}).[errors]
}).[stylesheets]
That's much verbose. Proposal is to introduce new syntax that allows to shorthand such queries and allow jora optimize it in the future.
The initial versions of proposed syntax was:
sites.{ stylesheets!.{ errors!.[name="ParseError] } } // or sites ! stylesheets ! errors.[name="ParseError]
But looks like the following a bit better:
sites ~ stylesheets ~ errors.[name="ParseError]
// or
sites ~ {
stylesheets ~ errors.[name="foo"],
scripts ~ errors,
alias: stylesheets ~ errors.[name="bar"],
}
{ foo: 123, bar: nothing, baz: false ? 456 : nothing } -> { foo: 123 }
[1, nothing, false ? 456 : nothing, 2, 3] -> [1, 2, 3]
Solve problem with map&filter
[1, 2, 3, 4].($ % 2 ? $ * 5 : nothing) -> [5, 10]
In expressions nothing is treating as undefined or false, however it's equal to inself only:
nothing or 1 // 1
nothing ? true : false // false
nothing and 1 // nothing
nothing + 5 // 5
nothing - 5 // -5
5 - nothing // 5
nothing = nothing // true
nothing = false // false
nothing = undefined // false
nothing in [] // false
nothing in [nothing] // false
{ [nothing]: 123 } // {}
When returned by query, it turns into undefined
jora('nothing')() // undefined
Nothing is represented as a Symbol internally, the single way it can leak from a query runtime is by exposed function that returns nothing
const fn = jora('=>nothing')() // function() { ... }
fn() // Symbol(nothing)
Under consideration:
- What about custom methods, should they have an access to nothing? Pros: it can return nothing as other methods. Cons: It may lead to nothing poisoning, i.e. method may add
nothing
value to arrays or objects. If not, functions should be "wrapped" when passing to custom method if it potentially can producenothing
(e.g.myMethod(=>nothing)
) - Probably add a return guard, that wraps query returned function
function wrapper(...args) { const res = fn(...args); return res === nothing ? undefined : res }
? Alternative: wrap a function when it potential goes outside. - New
left ?? right
operator: return the right side when left side is truthy, andnothing
otherwise
Real life example:
$resolve: => (
$children: children.map($resolve).[];
no $children ? null : { ..., $children}
);
something.map($resolve).[]
could be simplified (map().[]
remove)
$resolve: => (
$children: children.map($resolve).sort(name asc);
no $children ? nothing : { ..., $children } // or: $children ?? { ..., $children }
);
something.map($resolve)
foo | size() + bar.smth() | baz
Needed to simplify making subquery for a query, for example: foo.bar | subquery
that's the same as (foo.bar).subquery
;
It can be useful in case we want to treat a query result value as a scalar. At the moment we can't produce an object from an array that way [1,2,3].({ sum: ??, size: ?? })
, since map will be applied to each element of array. Possible workarounds:
- Using a variable:
$ar: [1,2,3]; { sum: $ar.sum(), size: $ar.size() }
- Using a function defined via variable (when will be possible to use a variable as a method):
$toMap: { sum: sum(), size: size() }; [1,2,3].$toMap()
In both cases, we can avoid variables using pipeline operator: [1,2,3] | { sum: sum(), size: size() }
It also can be used to simplify expressions avoiding mapping
{ foo: ..., bar: ..., baz: ... }.(foo + bar + baz)
// ->
{ foo: ..., bar: ..., baz: ... } | foo + bar + baz
or
$a.bar + $a.baz
// ->
$a | bar + baz
Pipeline operator produces a regular query, so may be used in any place where a query is applicable, i.e. query.({ foo: $ | { ... }, baz: $baz | a + b })
.
It's allowed to define variables on in the beggining of a chain, since they produce a block. E.g. query | $a: ...; $a + bar
, it's equivalent of using parentheses, i.e. query | ($a: ..; $a + bar)
Blocker:
$$
as variable inside a function. Is it to be a second param in a function? It may to be confusing where$$
comes from, however it should have the same behavious as$
has. It also prevent us from arguments for a function (as a workaround we can pass an object as current to function when several params is needed, i.e. instead ofquery.method(foo, bar)
doquery.({ value: $, foo, bar }).method()
or{ value: query, foo, bar }.method()
).
.{fn}
.{initValue fn}
.{=> $ < $$ ? $ : $$} // min
.{0 => $$ + $} // sum
entries().{{} => { ...$$, [key]: value }} // zip
['foo', 'bar', 'baz'].{=> $$ + ', ' + $} // join (result: "foo, bar, baz")
---
$avg: ($fn) => .{=> $$ + $fn()} / size();
$avg(<price>)
== old
|$$ + count|
|0|->|$$ + value|
.group(=>name).|$$ + value.size()|
.group(=>name).|min|
|=>$$ + $|
|$a=>$a+value|
|[]|->|$a=>$a+value|
- !
|=>$$ + value|
|||[]=>$$ + value|
|||$a:[]=>$a + value|
|||foo + bar or baz => $$ + value|
- !!!
|=>$$ + value|
|||[], =>$$ + value|
|||[], $a=>$a + value|
|||foo + bar or baz, => $$ + value|
|||min|
|||0, min|
(|{count:0,sum:0},=>{count:$$.count+1, sum:$$.sum + value},=>sum/count|
) - !!
|=>$$ + value|
|||[]|=>$$ + value|
|||[]|$a=>$a + value|
|||foo + bar or baz|=> $$ + value|
- !
|=>$$ + value|
|||[] => $$ + value|
|||$a; [] => $a + value|
|||foo + bar or baz => $$ + value|
- imposible to use references to fn
foo.bar.min||
foo.bar.sum|amount| // foo.bar.reduce(sum(fn)) -> foo.bar.reduce((r, i) => r + fn(i), 0)
foo.bar.|=> $$ + amount|
foo.bar.|=> $$ + amount => $$ / 100|
foo.bar.|0 => $$ + amount => $$ / 100|
foo.bar.|0 => $$ + num => $$ / size()| // avg
foo.bar.|($sum:0) => $sum + num ($sum) => $sum / size()|
foo.bar.|undefined sum final(?)|
foo.bar.|undefined sum(=>amount) final(?)|
foo.bar.|undefined sum(<amount>) final(?)|
foo.bar!length
foo.bar.|init; step; final|
fn|e|
|e? e e?|
.fn|e|
.|e? e e?|