Skip to content

Code Snippets Comparisons

Ilya Sher edited this page Mar 3, 2024 · 16 revisions

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.

User Agent String Building (Go)

Original: https://github.com/moby/moby/blob/4f0d95fa6ee7f865597c03b9e63702cdcb0f7067/pkg/useragent/useragent.go

Overhead

Go

package useragent // import "github.com/docker/docker/pkg/useragent"

import (
	"strings"
)

NGS

Currently none. Maybe later, when NGS explicitly supports very large projects.

Defining the Type and Basic Operations on the Type

Go

// VersionInfo is used to model UserAgent versions.
type VersionInfo struct {
	Name    string
	Version string
}

NGS

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

Version Info Validation

Go

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
}

NGS

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))
	})
}

Creating the Full User Agent String

Go

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

NGS

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(' ')
}

TC39 proposal for Object.fromEntries (JavaScript)

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.

The proposed functionality

JavaScript (proposed functionality)

obj = Object.fromEntries([['a', 0], ['b', 1]]); // { a: 0, b: 1 }

NGS (existing functionality)

obj = Hash([['a', 0], ['b', 1]])

Current code without the proposed feature

JavaScript

obj = Array.from(map).reduce((acc, [ key, val ]) => Object.assign(acc, { [key]: val }), {});

NGS

# 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))

Object-to-object property transformations

From the proposal's "Motivating examples"

JavaScript (proposed functionality)

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 }

NGS (existing functionality)

Transformation to and from Array is not need as there are convenient methods for manipulating Hashes.

# 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)

GitHub Issues Numbers and Titles

Original: https://elv.sh/

Elvish

curl https://api.github.com/repos/elves/elvish/issues |
	from-json | explode (all) |
	each [issue]{ echo $issue[number]: $issue[title] } |
	head -n 11

NGS

Alternative 1 - more readable

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 first n items.

Alternative 2 - shorter

``curl https://api.github.com/repos/elves/elvish/issues``.limit(11) / {"${A.number}: ${A.title}"} % echo
  • / is a "map" operator: x / f is equivalent to x.map(f). Use of / is not recommended for clarity reasons.
  • { ... } is a shortcut syntax for anonymous function. { ... } is equivalent to F(A=null, B=null, C=null) { ... }.
  • % is an "each" operator: x % f is equivalent to x.each(f). Use of % is not recommended for clarity reasons.

Astronauts from JSON API

Original: https://acha.ninja/blog/json-in-janet-shell/

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")))

NGS

Alternative 1 - closer to original

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

Alternative 2 - formatted table output

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

Check that a binary is installed

Python

return_code = os.system("zip")
if return_code != 0:
	raise SystemError("The zip binary is missing")

NGS

assert(Program("zip"))

script (Go Library)

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.

Read a file

script

contents, err := script.File("test.txt").String()

NGS

contents = read("test.txt")

Count lines in file

script

numLines, err := script.File("test.txt").CountLines()

NGS

numLines = read("test.txt").lines().len()

Count lines having a string

script

numErrors, err := script.File("test.txt").Match("Error").CountLines()

NGS

numErrors = read("test.txt").lines().count(Ifx("Error"))

Fixed string grep-like functionality

script

script.Stdin().Match("Error").Stdout()

NGS

read().Lines().filter(Ifx("Error")).echo()

Fixed string grep-like functionality on files from ARGV

script

Only first ten results

script.Args().Concat().Match("Error").First(10).Stdout()

NGS

ARGV.map(lines + read).flatten().filter(Ifx("Error")).limit(10).each(echo)

Fixed string grep-like functionality on files from ARGV

script

Only first ten results. Append to a file.

script.Args().Concat().Match("Error").First(10).AppendFile("/var/log/errors.txt")

NGS

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 (dictionary) by Keys

Filter Hash to only have keys not starting with underscore.

Python

args = {k: v for k, v in args.items() if not k.startswith("_")}

NGS - Alternative 1

args = args.rejectk(Pfx('_'))

NGS - Alternative 2

args .= rejectk(Pfx('_'))

objectsAreSame()

Custom comparison of two data structures.

TypeScript

Source: https://github.com/iasql/iasql-engine/blob/ef5968c4833ce46170042824d517903e8d3de8cf/src/services/aws-diff.ts

// 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';
}

NGS

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 Objects) will only return true for the same object.

mergePrincipal() - from AWS CDK

Values from source hash are merged into corresponding array value in target hash. Handles missing keys in target and arrays and scalars in source.

TypeScript

Original: https://github.com/aws/aws-cdk/blob/efbe6debaf1ccebbcd884912ccb38cb13a989061/packages/aws-cdk-lib/aws-iam/lib/util.ts#L70-L97

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;
}

NGS

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 so scalar becomes [scalar].

echo(mergePrincipal({'a': [1]}, {'a': 2, 'b': 3, 'c': [4,5]})) gives {a=[1,2], b=[3], c=[4,5]}

Clone this wiki locally