-
Notifications
You must be signed in to change notification settings - Fork 41
Code Snippets Comparisons
This page contains random pieces of code and their counterparts in NGS, reminiscent of Rosetta Code.
I frequently feel the need to see how a piece of code would look in NGS. Here are some of the comparisons.
package useragent // import "github.com/docker/docker/pkg/useragent"
import (
"strings"
)
Currently none. Maybe later, when NGS explicitly supports very large projects.
// VersionInfo is used to model UserAgent versions.
type VersionInfo struct {
Name string
Version string
}
doc VersionInfo is used to model UserAgent versions.
type VersionInfo
F init(vi:VersionInfo, name:Str, version:Str) init(args())
F Str(vi:VersionInfo) "${vi.name}/${vi.version}"
func (vi *VersionInfo) isValid() bool {
const stopChars = " \t\r\n/"
name := vi.Name
vers := vi.Version
if len(name) == 0 || strings.ContainsAny(name, stopChars) {
return false
}
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
return false
}
return true
}
F is_valid(vi:VersionInfo) {
STOP_CHARS = " \t\r\n/"
F contains_any(s:Str, chars:Str) s.any(X in chars)
[vi.name, vi.version].all(F(field) {
field and not(field.contains_any(STOP_CHARS))
})
}
func AppendVersions(base string, versions ...VersionInfo) string {
if len(versions) == 0 {
return base
}
verstrs := make([]string, 0, 1+len(versions))
if len(base) > 0 {
verstrs = append(verstrs, base)
}
for _, v := range versions {
if !v.isValid() {
continue
}
verstrs = append(verstrs, v.Name+"/"+v.Version)
}
return strings.Join(verstrs, " ")
}
F append_versions(base:Str, *version_info) {
guard version_info.all(VersionInfo)
not(version_info) returns base
base +? ' ' + version_info.filter(is_valid).map(Str).join(' ')
}
Original: https://github.com/tc39/proposal-object-from-entries
In this comparison JavaScript Object
is translated as NGS Hash
. That's the most appropriate NGS data structure for the presented use case in the proposal.
obj = Object.fromEntries([['a', 0], ['b', 1]]); // { a: 0, b: 1 }
obj = Hash([['a', 0], ['b', 1]])
obj = Array.from(map).reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {});
# Alternative 1
obj = collector/{} Arr(mymap).each(F(pair) collect(pair[0], pair[1]))
# Alternative 2 - implementation of the Hash() above, in "NGS (existing functionality)"
obj = collector/{} Arr(mymap).each(F(pair) collect(*pair))
From the proposal's "Motivating examples"
obj = { abc: 1, def: 2, ghij: 3 };
res = Object.fromEntries(
Object.entries(obj)
.filter(([ key, val ]) => key.length === 3)
.map(([ key, val ]) => [ key, val * 2 ])
);
// res is { 'abc': 2, 'def': 4 }
Transformation to and from Arr
ay is not need as there are convenient methods for manipulating Hash
es.
# Alternative 1
obj = { "abc": 1, "def": 2, "ghij": 3 }
res = obj.filterk(F(k) k.len() === 3).mapv(X*2)
# Alternative 2 - shorter syntax
obj = { "abc": 1, "def": 2, "ghij": 3 }
res = obj.filterk({A.len() === 3}).mapv(X*2)
Original: https://elv.sh/
curl https://api.github.com/repos/elves/elvish/issues |
from-json | explode (all) |
each [issue]{ echo $issue[number]: $issue[title] } |
head -n 11
issues = ``curl https://api.github.com/repos/elves/elvish/issues``
issues.limit(11).map(F(issue) "${issue.number}: ${issue.title}").each(echo)
-
``..``
is a syntax for "execute and parse output". -
issues
is a data structure corresponding to the parsed JSON from the API call. -
.limit(n)
limits the number of items by taking firstn
items.
``curl https://api.github.com/repos/elves/elvish/issues``.limit(11) / {"${A.number}: ${A.title}"} % echo
-
/
is a "map" operator:x / f
is equivalent tox.map(f)
. Use of/
is not recommended for clarity reasons. -
{ ... }
is a shortcut syntax for anonymous function.{ ... }
is equivalent toF(A=null, B=null, C=null) { ... }
. -
%
is an "each" operator:x % f
is equivalent tox.each(f)
. Use of%
is not recommended for clarity reasons.
Original: https://acha.ninja/blog/json-in-janet-shell/
(import json)
(var astronauts-in-space
(json/decode ($$ curl http://api.open-notify.org/astros.json)))
(each astronaut (astronauts-in-space "people")
(print
"Name: " (astronaut "name")
", Craft: " (astronaut "craft")))
astronauts_in_space = ``curl http://api.open-notify.org/astros.json``
each(astronauts_in_space.people, F(astronaut) {
echo("Name: ${astronaut.name}, Craft: ${astronaut.craft}")
})
-
``..``
is a syntax for "execute and parse output".
Output:
Name: Oleg Kononenko, Craft: ISS
Name: David Saint-Jacques, Craft: ISS
Name: Anne McClain, Craft: ISS
Name: Alexey Ovchinin, Craft: ISS
Name: Nick Hague, Craft: ISS
Name: Christina Koch, Craft: ISS
astronauts_in_space = ``curl http://api.open-notify.org/astros.json``
t = Table2::Table(astronauts_in_space.people)
echo(t)
Output:
name craft
Oleg Kononenko ISS
David Saint-Jacques ISS
Anne McClain ISS
Alexey Ovchinin ISS
Nick Hague ISS
Christina Koch ISS
name craft
return_code = os.system("zip")
if return_code != 0:
raise SystemError("The zip binary is missing")
assert(Program("zip"))
Source: https://github.com/bitfield/script
Note that all snippets below would have the overhead:
package main
import (
"github.com/bitfield/script"
)
func main() {
...
}
Any example which is longer in NGS is considered to be a bug and should be fixed.
contents, err := script.File("test.txt").String()
contents = read("test.txt")
numLines, err := script.File("test.txt").CountLines()
numLines = read("test.txt").lines().len()
numErrors, err := script.File("test.txt").Match("Error").CountLines()
numErrors = read("test.txt").lines().count(Ifx("Error"))
script.Stdin().Match("Error").Stdout()
read().Lines().filter(Ifx("Error")).echo()
Only first ten results
script.Args().Concat().Match("Error").First(10).Stdout()
ARGV.map(lines + read).flatten().filter(Ifx("Error")).limit(10).each(echo)
Only first ten results. Append to a file.
script.Args().Concat().Match("Error").First(10).AppendFile("/var/log/errors.txt")
Hmm.. it's easy to see that in NGS the use case of appending to a file did not appear. This is horribly long and cumbersome. Same goes for concatenation of files.
f = open(File("/var/log/errors.txt"), "a")
t = ARGV.map(lines + read).flatten().filter(Ifx("Error")).limit(10)
echo(f, t.Lines())
Filter Hash to only have keys not starting with underscore.
args = {k: v for k, v in args.items() if not k.startswith("_")}
args = args.rejectk(Pfx('_'))
args .= rejectk(Pfx('_'))
Custom comparison of two data structures.
// Returns true if the objects are the same, and false if they aren't
export function objectsAreSame(obj1: any = {}, obj2: any = {}): boolean {
// One is array of a single string and the other is string
if (isStringArray(obj1) && obj1.length === 1 && isString(obj2)) return obj1[0] === obj2;
if (isStringArray(obj2) && obj2.length === 1 && isString(obj1)) return obj2[0] === obj1;
// Both are array of strings
if (isStringArray(obj1) && isStringArray(obj2)) {
if (obj1.length !== obj2.length) return false;
obj1.sort();
obj2.sort();
return obj1.every((element, i) => element === obj2[i]);
}
// From https://stackoverflow.com/questions/44792629/how-to-compare-two-objects-with-nested-array-of-object-using-loop
let same =
Object.keys(obj1).filter(key => obj1[key] !== undefined).length ===
Object.keys(obj2).filter(key => obj2[key] !== undefined).length;
if (!same) return same;
for (const key of Object.keys(obj1)) {
if (isObject(obj1[key])) {
same = objectsAreSame(obj1[key], obj2[key]);
} else {
if (obj1[key] !== obj2[key]) {
same = false;
}
}
if (!same) break;
}
return same;
}
// Helper functions (not needed in NGS version)
function isStringArray(obj: unknown): obj is string[] {
return Array.isArray(obj) && obj.every(isString);
}
export function isString(obj: unknown): obj is string {
return typeof obj === 'string';
}
Note: remember that method lookup goes from bottom to top. This means that the top of the original function is at the bottom of NGS version.
F objectsAreSame(a, b) a == b # Potentially called from objectsAreSame(a:Hash, b:Hash)
F objectsAreSame(a:Hash, b:Hash) {
# null is used below because NGS has no "undefined"
a.rejectv(null).len() != b.rejectv(null).len() returns false
a.all(F(k, v) {
objectsAreSame(v, b[k])
})
}
# Note: `isStringArray()` call is not needed in NGS because:
# (1) The array check is performed when matching the arguments with parameters.
# (2) The check that each element is a string is short enough to be left in place and not to factor out: my_arr.all(Str)
F objectsAreSame(a:Arr, b:Arr) {
guard a.all(Str) and b.all(Str)
a.len() != b.len() returns false
a.sort() == b.sort()
}
F objectsAreSame(a:Arr, b:Str) a == [b]
F objectsAreSame(a:Str, b:Arr) [a] == b
It seems like ==
semantics in NGS allows for shorter code: ==
does element-wise comparison for arrays. In JavaScript, when comparing arrays (JavaScript Object
s) will only return true for the same object.
Values from source
hash are merged into corresponding array value in target
hash. Handles missing keys in target
and arrays and scalars in source
.
export function mergePrincipal(target: { [key: string]: string[] }, source: { [key: string]: string[] }) {
const sourceKeys = Object.keys(source);
const targetKeys = Object.keys(target);
// *** Removed part which was not clear ***
for (const key of sourceKeys) {
target[key] = target[key] ?? [];
let value = source[key];
if (!Array.isArray(value)) {
value = [value];
}
target[key].push(...value);
}
return target;
}
F mergePrincipal(target:Hash, source:Hash) {
source.each(F(key, value) {
target.dflt(key, []).push_all(value.ensure(Arr))
})
target
}
-
dflt()
sets a value for the key if it's not present, returning either the original value or the new one -
ensure()
converts the data, if necessary, making minimal changes, to match the given pattern. In our case, the pattern is the array type soscalar
becomes[scalar]
.
echo(mergePrincipal({'a': [1]}, {'a': 2, 'b': 3, 'c': [4,5]}))
gives {a=[1,2], b=[3], c=[4,5]}
NGS official website is at https://ngs-lang.org/