exonum-client icon indicating copy to clipboard operation
exonum-client copied to clipboard

Use Flow type system?

Open slowli opened this issue 6 years ago • 6 comments

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.

slowli avatar Jul 17 '17 21:07 slowli

@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.

therustmonk avatar Jul 18 '17 13:07 therustmonk

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.

pepyakin avatar Jul 20 '17 08:07 pepyakin

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

stanislav-tkach avatar Jul 20 '17 10:07 stanislav-tkach

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

pepyakin avatar Jul 20 '17 10:07 pepyakin

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

stanislav-tkach avatar Jul 20 '17 10:07 stanislav-tkach

@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.

vnermolaev avatar Oct 17 '17 12:10 vnermolaev