Library update: RSLike@3.0.0. Better TS types. Symbols and more

What's new

this release makes a massive update. Just look at these changes!

std. Standard library.

All changes apply to Option and Result objects.

Symbols for Option and Result objects

Implements Symbol.iterator, Symbol.asyncIterator, Symbol.search, Symbol.split, Symbol.inspect, Symbol.toPrimitive, Symbol.toStringTag

Example:

 // Before
const arr = Some([1, 2, 3]);
// error, for..of only supports with arrays
for (let el of arr) {
}

As a workaround, you may unwrap the value

// workaround
const arr = Some([1, 2, 3]);
// error, for..of now works with arrays
for (let el of arr.unwrap()) {
}

Now you can "just call for..of".

// now it works
const arr = Some([1, 2, 3]);
// error, for..of only supports with arrays
for (let el of arr) {
    // works now!
}

Note: This method will only yeild if the Option is Some

Note: throws UndefinedBehaviorError for Some(value) if value is not implements Symbol.iterator

More examples with Symbols implementation can found in the wiki

Symbol.hasInstance for Some, None, Ok and Err functions

Now you can call instanceof keyword for functions. From now you can skip importing ResultandOption classes.

Example:

// Before
import { Ok, Err, Result } from "@rslike/std";
const a = Ok(3);
a instanceof Ok; // false
a instanceof Result; // true

// now, less imports
import { Ok, Err } from "@rslike/std";
const b = Ok(3);
b instanceof Ok; // true
b instanceof Err; // false
Err("Some Err") instanceof Err; // true

Advanced Types inferring

Add more complex TS types to understand have value or not.

Example:

// Before
const a = Some(3);

a.isSome(); // TS type: boolean

// now
a.isSome(); // TS type: true

double unwrapping in match function for Result<Option<T>, E>

From now you just can use match a function with only 1 unwrapping.

If you're using Async or Bind functions - your result will be wrapped to Result<Option<_>>. To unwrap this result you must use double matching.

Example (before - 67 lines):

import { Bind, match, Err } from "@rslike/std";
function divide(a: number, b: number) {
    if (b === 0) Err("Divide to zero");
    if (a === 0) Ok(0);
    if (Number.isNaN(a) || Number.isNaN(b)) return undefined;
    return a / b;
}

const binded = Bind(divide);
const fn1 = binded(1, 1); // Result<Option<number | undefined>, string>
const fn2 = binded(NaN, 1); // Result<Option<undefined>, string>

const res1 = match(
    fn1, // or fn2
    (res) => {
        return match(
            res,
            (value) => console.log("value is:", value)
            () => console.log("value is None"))
    (err) => console.error(err);
);

console.log(res1); // value is: 1
console.log(res2); // value is None

Example (now - 27 lines):

import { Bind, match, Err } from "@rslike/std";
function divide(a: number, b: number) {
    if (b === 0) Err("Divide to zero");
    if (a === 0) Ok(0);
    if (Number.isNaN(a) || Number.isNaN(b)) return undefined;
    return a / b;
}

const binded = Bind(divide);
const fn1 = binded(1, 1); // Result<Option<number | undefined>, string>
const fn2 = binded(NaN, 1); // Result<Option<undefined>, string>

const res1 = match(
    fn1, // or fn2
    (value) => {
        console.log("value is:", value);
    },
    (err) => {
        if (err) console.error(err);
        else console.log("value is None");
    }
);

console.log(res1); // value is: 1
console.log(res2); // value is None

cmp. Comparison package

This package introduces custom Symbol implementation in global objects.

Compare package now registered in the global scope Symbol.compare, Symbol.partialEquals and Symbol.equals.

Symbol.compare

This symbol exists to compare 2 values - Self and another.

We recommend throwing UndefinedBehaviorError for uncomparable types.

This method should return a number.

  • If method returns 1 (or more) - Self > another

  • If method returns 0 - Self == another

  • If method returns -1 - Self < another

  • If method returns NaN - arguments have the same types but are not comparable to each other. E.g. NaN == NaN -> false

Symbol.compare implemented for Number, String, Boolean and Date objects. You can implement this trait for any object and use it with primitives.

Examples

Built-in

(5)[Symbol.compare](3); // 5 > 3 returns 1
"qwe"[Symbol.compare]("asd"); // 'a' > 'q'. returns -1

Custom implementation

class A {
  constructor(private readonly value: number) {}
  [Symbol.compare](this, another: unknown) {
    if (typeof another === "number") {
      return this.value - another;
    }
    throw new Error("unsupported type");
  }
}

const a = new A(5);

(5)[Symbol.compare](a); // A(5) - 5 = 0

Symbol.partialEquals

This method tests for this and other values to be equal, and is used by == (with type lossenes).

NOTE:other always will be unknown. Generic T only helps when used with typescript. Perform checks on your side.

NOTE: partialEquals function is used this to bind self result.

Symbol.partialEquals implemented for Number, String, Boolean and Date objects. You can implement this trait for any object and use it with primitives.

Best practice

accept other as unknown type

use function declaration for this binding if you need to use an original object to compare

for objects - use partialEquals since it can interpreted as "shallow" equality

Example

Built-in:

(5)[Symbol.partialEquals]("5"); // 5 == '5' -> true

Custom implementation

class A {
  constructor(private readonly value: number) {}
  [Symbol.partialEquals](this, another: unknown) {
    if (typeof another === "number") {
      return true;
    }
    throw new Error("unsupported type");
  }
}

const a = new A(5);

(6)[Symbol.partialEquals](a); // A(6) always is true for number

Symbol.equals

Type for equality comparisons which are equivalence relations.

other always will be unknown. Generic T only helps when used with typescript. Perform checks on your side.

built-in objects(Number, String, Boolean, Date) will throw UndefinedBehaviorError error for an object with [Symbol.equals] trait implementation but returns, not a boolean type

Best practice

Return "boolean" type without throwing an error

Example

Built-in

(6)[Symbol.equals]("6"); // 6 === '6' -> false

Custom implementation

import { type Eq, equals } from "@rslike/cmp";

enum BookFormat {
  Paperback,
  Hardback,
  Ebook,
}
class Book implements Eq {
  public isbn: boolean;
  public format: BookFormat;
  [Symbol.equals](other: Book) {
    return other instanceof Book && this.isbn == other.isbn;
  }
}

const book1 = new Book();
book1.isbn = true;
const book2 = new Book();
book2.isbn = true;

equals(book1, book2); // true
equals(book1, 5); // false
equals(book1, true); // false

Utility functions

compare

Called Symbol.compare to compare 2 arguments between each other and returns number.

In most cases, it returns 4 possible numeric values and can be interpreted as:

  • result is >=1 - the first argument is more than incoming

  • result is 0 - arguments are the same

  • result is <=-1 - the second argument is more than incoming

  • result is NaN - arguments have the same types but are uncomparable between each other (e.g. comparing NaN with NaN gives NaN, since we cannot compare 2 NaNs)

If neither of the arguments implements Symbol.compare trait - compareFn(3rd argument) will be called.

Throws

  • UndefinedBehaviorError if compareFn is not defined and neither of the arguments implements Symbol.compare trait

  • UndefinedBehaviorError if compareFn is not a function

  • UndefinedBehaviorError if compareFn returns not a number type (e.g. string or boolean)

Example

import { compare } from "@rslike/cmp";

compare(2, 3); // -1, 3 > 2
compare(2, {
  [Symbol.compare]() {
    return 500;
  },
}); // 500, object returns 500

compare(2, {
  [Symbol.compare]() {
    return 'hello world'';
  },
}); // throws undefiend behavior. Symbol.compare should return a number

partialEquals

Partial equals. Same as ==(type loose comparison). You can interpretate it as "shallow" equal

Throws

  • UndefinedBehaviorError for a and b objects without implementation Symbol.partialEquals trait or if Symbol.partialEquals trait returns not boolean type.

Example

import { partialEquals } from "@rslike/cmp";

partialEquals(5, "5"); // true

equals

Equals. Same as ===(type looseness comparison).

Called [Symbol.equals] implementation for the first or another argument. If neither of the arguments implements [Symbol.equals] trait then equalityFn the argument will be called. Else - UndefinedBehaviorError will be throws

Throws

UndefinedBehaviorError for a and b objects without implementation "Symbol.equals" trait

UndefinedBehaviorError if [Symbol.equals] trait returns not boolean type(e.g. string or number)

Types

Eq

type for checking equality. requires to implement [Symbol.equals]

Type for equality comparisons which are equivalence relations.

This means, that in addition to a == b and a != b being strict inverses, the equality must be (for all a, b and c):

reflexive: a == a; symmetric: a == b implies b == a; and transitive: a == b and b == c implies a == c. This property cannot be checked by the compiler, and therefore Eq implies PartialEq, and has equality extra method.

Example

specify that your type implements Eq.

enum BookFormat {
  Paperback,
  Hardback,
  Ebook,
}
class Book implements Eq {
  public isbn: number;
  public format: BookFormat;
  [Symbol.equals](another) {
    return other instanceof Book && this.isbn == other.isbn;
  }
}

Ord

Requires to implement Symbol.compare trait

Example

import { type Ord } from "@rslike/cmp";

class MyArr<number> implements Ord {
  readonly currentIndex: number;
  [Symbol.compare](other: number) {
    return this.currentIndex - other;
  }
}