Skip to content

Instantly share code, notes, and snippets.

@lahmatiy
Last active July 13, 2023 21:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lahmatiy/d5af7a987e9548e80eae5f46e6edc931 to your computer and use it in GitHub Desktop.
Save lahmatiy/d5af7a987e9548e80eae5f46e6edc931 to your computer and use it in GitHub Desktop.
Jora todo

TODO

  • 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, or expr.is('array', 'number'), .type() method
  • type casting: smth like expr as array (as number, as int, as string etc), or expr.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. $(...)() or foo.$(...)() – 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?), where hashFn (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)

Done

  • 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 or foo.bar.$baz -> .[$foo]?
  • (?) parenthesis invocation, i.e. ($fnA or $fnB)(...) (looks like a bad idea)
  • sort function and function list, i.e. foo asc and foo 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() or foo.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 to ar.($['foo bar'])
  • string template (draft)
  • reduce() (see below)
  • make the lowest priority for asc & desc, e.g. in foo + bar desc only bar desc converts into function
  • indexOf() / lastIndexOf()

Filtering subqueries

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"],
}

nothing

{ 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 produce nothing (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, and nothing 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)

Pipeline operator

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)

Reduce

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 of query.method(foo, bar) do query.({ 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?|
{
view: 'list',
data:
}
list->{
data: foo.bar,
checked: false,
item: text->'hello world!'
}
block -> {
className: 'dir-file-count',
when: children,
data:
$files = ..children.[no children];
{
count: $files.size(),
noOwner: $files.[no owner.ownerId].size(),
$files
},
content:
text -> count + (count > 1 ? " files" : " file")',
block -> {
className: 'bad',
when: noOwner,
content: text -> noOwner == count
? "all unowned"
: "unowned " + noOwner + (noOwner > 1 ? " files" : " file")'
}
}
block {
className: 'dir-file-count',
when: children,
data:
$files = ..children.[no children];
{
count: $files.size(),
noOwner: $files.[no owner.ownerId].size(),
$files
},
content:
text <- count + (count > 1 ? " files" : " file")',
block {
className: 'bad',
when: noOwner,
content: text <- noOwner == count
? "all unowned"
: "unowned " + noOwner + (noOwner > 1 ? " files" : " file")'
}
}
----
children ?? -> block {
className: 'dir-file-count',
content:
$files = ..children.[no children];
{
count: $files.size(),
noOwner: $files.[no owner.ownerId].size(),
$files
} ->
count + (count > 1 ? " files" : " file") -> text,
noOwner ??
noOwner == count
? "all unowned"
: "unowned " + noOwner + (noOwner > 1 ? " files" : " file")'
-> block {
className: 'bad',
content: -> text
}
}
}
data.block(config)
data -> block config
block config <- data
data -> method config <- data
data.method(arg1, arg2, ...)
data -> method arg1, arg2 -> method
method arg1, arg2 <- data
---
e = ...
SYMBOL object === SYMBOL ( object )
??
block -> SYMBOL arguments
SYMBOL arguments <- block
---
block(data, ^context, config) {
discovery.render(el, 'block', config, data, context)
}
discovery.render(el, viewNameOrFn, config, data, context)
(files + foo) ...
files + foo -> ...
files + foo ->
repo.files.($file:$;packages.({$file, name})).group(<name>, <file>).({name:key,files:value}).sort(<name>)
repo.files.($file:$;packages.({$file, name})) -> group <name> -> .({name:key,files:value}) -> sort(name asc, size desc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment