Skip to content

FAQ

Status of Straits

Straits is still experimental. It works reliably and it's proving itself very useful, but a few important features are still missing and major things (even the syntax for our statements) might be tweaked in the future.

Symbol versioning

Currently, if two different versions of a library that uses straits are used in the same project, all the symbols exposed by such library are duplicated.

Let's consider a concrete example: scontainers. Let's say that the semantics of the flatten trait changes. The version of scontainers will be bumped, and new projects will start using the new version. Existing code might continue using the old one for a while. When both versions of scontainers are loaded in a project, two different full sets of traits will be created and coexist. The two versions of the same symbols (e.g. map, as well as flatten) will both be implemented for the standard types. The containers you defined, instead, will only implement the version of the traits your code is using. Some other modules used in the project (the ones that use the other version of scontainers) won't be able to use the container traits implemented on your objects.

The current behavior could be ok, but a different behavior might be preferable. The traits whose semantics didn't change should use the same symbol even among two different versions of scontainers. Only the traits whose semantics changed should use different symbols (and thus different implementations). This requires somehow versioning the symbols. It's something that a library (e.g. scontainers) could already do, but we believe that there should be a standard way and that straits.utils should offer an API to aid the effort.

Trait set extensibility

In most programming languages supporting traits (e.g. rust, haskell, go etc), one can automatically implement new traits for all the types that implement some other traits.

This cannot be easily done in JavaScript, as it's a dynamic language and virtually anything can implement traits at any point of the execution. Keeping a database of which objects are implementing which trait is not only very resource consuming, but would also result in memory leaks.

This feature would be very useful in JavaScript as well. Think of scontainers: a new trait whileEach could easily be implemented for every object that implements forEach. But how can we achieve that? As above, we need to choose a convention to extend traits, and straits.utils should offer aiding functions.

use trait t from traitSet

It might happen that two different trait sets expose traits with the same name. For instance straits.math.log is used to compute the logarithm function (i.e. Math.log), while straits.console.log is used to print values to the console (i.e. console.log). If your code wants to both use traits * from straits.math and use traits * from straits.console, .*log() can not currently be used.

An advanced version of the use traits statement could help us here. Imagine the following piece of code:

1
2
3
4
import * as straits from 'straits';
use traits * from straits.math;
use traits log, * from straits.console;
7.*log();

It means that all the traits are imported both from straits.math and from straits.console, but we are going to use the log symbol from straits.console.

Technical details

How does straits scoping work?

The .* operator looks for its right identifier in a different scope from the regular variable's scope. The scope it uses is populated by the use traits statements.

The traits scopes have a visibility similar to regular scopes: they are valid only for the current block and all its nested ones. Traits used in inner scopes don't override those defined in outer scopes though, as shown in the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const traits1 = { x:Symbol() };
use traits * from traits1;

{
    const traits2 = { x:Symbol() };
    use traits * from traits2;

    // Error!
    // Symbol x offered by multiple trait sets.
    [].*x;
}

This is to avoid problems with API changes: imagine in fact that at the beginning of the development only traits1 defined the x trait. Code everywhere was written relying on that specific trait. If later during the development traits2 adds an x trait, the existing code should not start using that one, as the semantics may differ.

Ideally it should be possible to explicitly say in which trait set to look for a trait:

1
2
3
4
5
6
7
8
9
const traits1 = { x:Symbol() };
use traits x, * from traits1; // `x` is only looked for in `traits1`

{
    const traits2 = { x:Symbol() };
    use traits * from traits2;

    [].*x; // same as `[][traits1.x]`
}

@straits/babel does not implement this syntax yet.

Common errors

SyntaxError: Unexpected identifier; SyntaxError: Unexpected token *

The use traits statement and .* operator aren't standard JavaScript. They're a proposed extension. Currently, the only way to use them is transpiling them to valid JavaScript using @straits/babel.

No trait set is providing symbol …

One of the traits accessed with the .* operator is not provided by any trait set:

1
2
3
4
const traitSet = {};
use traits * from traitSet;

[].*x;

Symbol … offered by multiple trait sets

One of the traits accessed with the .* operator is provided by 2 or more trait sets:

1
2
3
4
5
6
const traitSet1 = { x:Symbol() };
const traitSet2 = { x:Symbol() };
use traits * from traitSet1;
use traits * from traitSet2;

[].*x;