exonum / exonum-client

JavaScript client for Exonum blockchain
Apache License 2.0
65 stars 33 forks source link

Use Flow type system? #52

Open slowli opened 7 years ago

slowli commented 7 years ago

Since we (supposedly) build secure software, we need to worry about type safety everywhere (including the client), and allow to take care of type safety for developers using the client. Flow seems like a straightforward way to accomplish this; although there are some alternatives (e.g., TypeScript or Elm, or even compiling Rust code directly to wasm or asm.js).

@DenisKolodin Do you think Elm would be an optimal way to pursue? I'm a bit worried that a "fully" functional Elm's approach can have certain performance implications. For example, the tutorial on lists does not seem to unroll recursion, not even in the tail-form (i.e.,

length : List a -> Int
length list = length2 list 0

length2 : List a -> Int -> Int
length2 list acc =
  case list of
    [] ->
        acc
    first :: rest ->
        length2 rest 1 + acc

so it doesn't work with lists of length more than several thousand. Am I doing something wrong, or is this just how Elm rolls (heh)? Also, in general it seems Flow is a bit more supported than Elm, although I may be biased.

therustmonk commented 7 years ago

@slowli Elm is awesome for large web-applications, but it's unsuitable for CPU-bound tasks like cryptography algorithms, because Elm produces smart wrappers to maintain functional features. For example Elm generates the following code for Exonum.newMessage binding:

var _exonum$exonum_ico_client$Bind_Exonum$newMessage = function (declaration) {
    var struct = A2(
        _elm_lang$core$Json_Decode$decodeValue,
        _elm_lang$core$Json_Decode$value,
        _elm_lang$core$Json_Encode$object(
            {
                ctor: '::',
                _0: {
                    ctor: '_Tuple2',
                    _0: 'size',
                    _1: _elm_lang$core$Json_Encode$int(declaration.size)
                },
                _1: {
                    ctor: '::',
                    _0: {
                        ctor: '_Tuple2',
                        _0: 'network_id',
                        _1: _elm_lang$core$Json_Encode$int(declaration.networkId)
                    },
                    _1: {
                        ctor: '::',
                        _0: {
                            ctor: '_Tuple2',
                            _0: 'protocol_version',
                            _1: _elm_lang$core$Json_Encode$int(declaration.protocolVersion)
                        },
                        _1: {
                            ctor: '::',
                            _0: {
                                ctor: '_Tuple2',
                                _0: 'service_id',
                                _1: _elm_lang$core$Json_Encode$int(declaration.serviceId)
                            },
                            _1: {
                                ctor: '::',
                                _0: {
                                    ctor: '_Tuple2',
                                    _0: 'message_id',
                                    _1: _elm_lang$core$Json_Encode$int(declaration.messageId)
                                },
                                _1: {
                                    ctor: '::',
                                    _0: {
                                        ctor: '_Tuple2',
                                        _0: 'fields',
                                        _1: _exonum$exonum_ico_client$Bind_Exonum$serializeFields(declaration.fields)
                                    },
                                    _1: {ctor: '[]'}
                                }
                            }
                        }
                    }
                }
            }));
    var _p4 = struct;
    if (_p4.ctor === 'Ok') {
        return _exonum$exonum_ico_client$Native_Exonum.newMessage(_p4._0);
    } else {
        return _elm_lang$core$Native_Utils.crashCase(
            'Bind.Exonum',
            {
                start: {line: 116, column: 9},
                end: {line: 118, column: 45}
            },
            _p4)(_p4._0);
    }
};

//// BASIC STUFF ////

function F2(fun)
{
  function wrapper(a) { return function(b) { return fun(a,b); }; }
  wrapper.arity = 2;
  wrapper.func = fun;
  return wrapper;
}

function A2(fun, a, b)
{
  return fun.arity === 2
    ? fun.func(a, b)
    : fun(a)(b);
}
// ...
function A9(fun, a, b, c, d, e, f, g, h, i)
{
  return fun.arity === 9
    ? fun.func(a, b, c, d, e, f, g, h, i)
    : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
}

//// LIST STUFF ////

var Nil = { ctor: '[]' };

function Cons(hd, tl)
{
    return {
        ctor: '::',
        _0: hd,
        _1: tl
    };
}

function append(xs, ys)
{
    // append Strings
    if (typeof xs === 'string')
    {
        return xs + ys;
    }

    // append Lists
    if (xs.ctor === '[]')
    {
        return ys;
    }
    var root = Cons(xs._0, Nil);
    var curr = root;
    xs = xs._1;
    while (xs.ctor !== '[]')
    {
        curr._1 = Cons(xs._0, Nil);
        xs = xs._1;
        curr = curr._1;
    }
    curr._1 = ys;
    return root;
}

Elm, TypeScript and other JavaScript transpilers adds quite overhead to every function call (however I feel that every transpiler result suits good for JIT compiler).

Flow seems better for me: we still have raw (high-speed) JavaScript code, but we get a powerful type checking.

Also we need to try Rust-to-wasm approach. It looks ultimate, but I have no idea about its performance.

pepyakin commented 7 years ago

Just came here to mention rust-to-wasm, but it is already here. 😆

So, I decided to publish my experiments with Rust-WebAssembly approach here: it is stripped duct-taped version of exonum core. may be it will help to start assessing this approach.

Good thing about this approach is that one can share it's types and message definitions (for example). Bad thing is that Rust and WebAssembly story isn't complete yet.

Anyway, I believe that Rust-WebAssembly approach is worth exploring.

stanislav-tkach commented 7 years ago

I suppose it is worth creating a separate issue about WebAssembly.

pepyakin commented 7 years ago

@DarkEld3r Do you mean here or in exonum-core?

stanislav-tkach commented 7 years ago

@pepyakin I suppose it is more related to the "exonum-client".

vnermolaev commented 6 years ago

@DenisKolodin I do not think it's correct to compare Elm and Typescript (or Flow). While the former is a full-blown language targeting web that gets transpiled to JS (much like, say, Dart), the latter performs static type checks. TS leaves no trace of itself after transpilation producing a pure JS code free of any run-time type checks.

I personally favor TS mainly due to its design goals and the fact that TS lives up to them.

And from what I see in the present exonum-client you already write something very similar to TS but employing jsdoc annotations. In case of TS they would have to be moved into definitions of the functions.