promhx vs. tink_core

260 views
Skip to first unread message

Nathan Hüsken

unread,
Apr 22, 2014, 7:44:13 AM4/22/14
to haxe...@googlegroups.com

Hey,

I was recently informed about promhx in this list. Now I see the “future” and “signal” feature in tink_core, and I am confused:

  • What is the difference between futures and signals in tink_core to promises and streams in promhx?
  • Why do this libraries coexist?

Thanks!
Nathan

signature.asc

Juraj Kirchheim

unread,
Apr 22, 2014, 11:33:00 AM4/22/14
to haxe...@googlegroups.com
Let me answer the question in reverse order:

> Why do this libraries coexist?

Because they can :)
But most importantly, because they have a different approach to things.
I would also point out that msignal provides an alternative signal
implementation which is also worth considering depending on your
particular needs.

> What is the difference between futures and signals in tink_core to promises
> and streams in promhx?

For one, as far as I understand Justin tried to stick close to the
Promise pseudo-standard
(http://promises-aplus.github.io/promises-spec/) for JavaScript. I was
primarily interested in building something that makes sense from a
Haxe perspective.

Quite frankly I wasn't really aware of promhx when I started out. But
since I had carte blanche, there are quite a few differences.

1. Futures and Signals are readonly. You have to use a SignalTrigger
and a FutureTrigger to be able to write data (i.e. resolve the
promise). This is similar to Scala, where a Future is a readonly
Promise. I considered this important, given that you may not want to
expose write access to client code.

2. In Promhx you cannot unregister handlers. This is in accordance to
the standard. But particularly for signals/streams, this is
impractical.

3. The tink_core library in itself is entirely macro free and declares
primitives and the most common operations on them. Promhx comes with a
number of convenience macros and adapters for JavaScript/jQuery stuff.
I am working to add some of these things in other parts of Tinkerbell,
but to me it was important to keep the core lightweight and small.

4. In tink_core the possibility of errors is expressed through
Outcome. That's because I feel it's important not to blur the line
between systematic failure (e.g. I/O doesn't work because the real
world is a gruesome place) and human failure (e.g. code that causes a
null pointer exception), commonly distinguished as errors and
exceptions. This makes errors visible and first class. So in tink_core
I can distinguish `Future<Result>` and `Future<Outcome<Result,
Error>>` (or `Surprise<Result, Error>` as I like to alias it).
The latter can fail. But if I happen to have an error recovery
function `Error->Result` I can turn the latter to the former and
express that through the type system. As opposed to that, Promhx
treats errors and exceptions the same way and therefore you've got
this separate untyped mechanism for error handling. It also has ways
to recover errors, but they elude the type system.

All that stuff is explained in tink_core's rather extensive readme.
Still, if you have further question don't hesitate to ask ;)

I don't mean to imply that one library is better than the other, even
though my comparison was unsurprisingly biased. But I hope to have
answered your question on why the two are separate things. Either way,
I think both libraries are small enough that it's not really a life
time commitment to use one or the other ;)

Regards,
Juraj

Justin Donaldson

unread,
Apr 22, 2014, 11:37:45 AM4/22/14
to Haxe
It's not just tink_core and promhx, there's also Franco's thx.react:

And Atry's haxe-continuation is certainly worth a mention:

(apologies if I missed anyone else)

Honestly, tink_core is worth checking out aside from async utilities.  So much good stuff in there.   I even used it for a while in promhx for some macro methods, up until I stumbled across a strange bug we couldn't sort out.

That said, promhx is a bit different.  I see Juraj is beating me slightly to a more thorough explanation, so I'll add more in a reply there.


Justin Donaldson

unread,
Apr 22, 2014, 12:27:32 PM4/22/14
to Haxe


For one, as far as I understand Justin tried to stick close to the
Promise pseudo-standard
(http://promises-aplus.github.io/promises-spec/) for JavaScript. I was
primarily interested in building something that makes sense from a
Haxe perspective.

FWIW, I did read up on the spec, follow the discussions, and provide an initial working implementation.
However, I'm now convinced that the spec is misguided, and that following it means we lose a lot of great capabilities (mainly surrounding composability, which tink_core dcos describe a little).
 
I can fill in some more details below... I'm glad that this thread exists, I don't think anyone has compared all of the libraries yet.


Quite frankly I wasn't really aware of promhx when I started out. But
since I had carte blanche, there are quite a few differences.

1. Futures and Signals are readonly. You have to use a SignalTrigger
and a FutureTrigger to be able to write data (i.e. resolve the
promise). This is similar to Scala, where a Future is a readonly
Promise. I considered this important, given that you may not want to
expose write access to client code.

Yes, promhx provides much simpler wrapper in that sense.  I suppose that's something I could provide read only versions eventually.
 

2. In Promhx you cannot unregister handlers. This is in accordance to
the standard. But particularly for signals/streams, this is
impractical.

That's something I'm working on improving still.  However, there's a "pause()" method for streams that will momentarily stop the stream, and an "end()" method that is permanent.
 

3. The tink_core library in itself is entirely macro free and declares
primitives and the most common operations on them. Promhx comes with a
number of convenience macros and adapters for JavaScript/jQuery stuff.
I am working to add some of these things in other parts of Tinkerbell,
but to me it was important to keep the core lightweight and small.

Yep, all of these concepts are somewhat new.  I wanted to provide a "batteries included" version so that you can immediately start using these techniques with more established frameworks, etc.  


4. In tink_core the possibility of errors is expressed through
Outcome. That's because I feel it's important not to blur the line
between systematic failure (e.g. I/O doesn't work because the real
world is a gruesome place) and human failure (e.g. code that causes a
null pointer exception), commonly distinguished as errors and
exceptions. This makes errors visible and first class. So in tink_core
I can distinguish `Future<Result>` and `Future<Outcome<Result,
Error>>` (or `Surprise<Result, Error>` as I like to alias it).
The latter can fail. But if I happen to have an error recovery
function `Error->Result` I can turn the latter to the former and
express that through the type system. As opposed to that, Promhx
treats errors and exceptions the same way and therefore you've got
this separate untyped mechanism for error handling. It also has ways
to recover errors, but they elude the type system.

 Promhx has special error handling, since I think dealing with errors in async code ranges from tedious to unpredictable.  You can still treat certain errors as first class values (via a wrapped Outcome<T> or similar), but promhx provides a totally separate mechanism for handling "everything else".  
For instance, "catchError()" will function like a "catch" directive applied to the promise chain up until that point.  "errorThen" will function like a "then", but will map any errors back to useful values, so that the rest of the chain can still work from there.  It's more of a safety net. 


Best,
-Justin

Juraj Kirchheim

unread,
Apr 26, 2014, 5:35:50 AM4/26/14
to haxe...@googlegroups.com
On Tue, Apr 22, 2014 at 6:27 PM, Justin Donaldson <jdona...@gmail.com> wrote:
>>
>> For one, as far as I understand Justin tried to stick close to the
>> Promise pseudo-standard
>> (http://promises-aplus.github.io/promises-spec/) for JavaScript. I was
>> primarily interested in building something that makes sense from a
>> Haxe perspective.
>
> FWIW, I did read up on the spec, follow the discussions, and provide an
> initial working implementation.
> However, I'm now convinced that the spec is misguided, and that following it
> means we lose a lot of great capabilities (mainly surrounding composability,
> which tink_core dcos describe a little).

I wouldn't go as far as calling it misguided. The JavaScript community
has a very peculiar culture and the Promise spec is a manifestation of
that ;)

>> Quite frankly I wasn't really aware of promhx when I started out. But
>> since I had carte blanche, there are quite a few differences.
>>
>> 1. Futures and Signals are readonly. You have to use a SignalTrigger
>> and a FutureTrigger to be able to write data (i.e. resolve the
>> promise). This is similar to Scala, where a Future is a readonly
>> Promise. I considered this important, given that you may not want to
>> expose write access to client code.
>
>
> Yes, promhx provides much simpler wrapper in that sense. I suppose that's
> something I could provide read only versions eventually.

Any kind of composability can be achieved on the read only versions,
so I'm not sure why it is coupled with writability.

To give you my honest opinion: I think this is actually vital. When I
revisited tink_core I considered to build on promhx. The error
handling I could have lived with, but this for me was a no-go.

>> 2. In Promhx you cannot unregister handlers. This is in accordance to
>> the standard. But particularly for signals/streams, this is
>> impractical.
>
> That's something I'm working on improving still. However, there's a
> "pause()" method for streams that will momentarily stop the stream, and an
> "end()" method that is permanent.

This is not at all the same. Say a button has a click stream and you
attach a listener and want to detach it again:

typedef Button = {
var clicked(default, null):Stream<MouseEvent>;
}

You don't want to pause that stream and affect any other listeners
that may be registered (now or in the future).
The approach is ok if you create the click stream on the fly (as with
your jquery thingy), but you can't do the above, which I think is a
pitty, because it makes for a nice type safe interface and it's not
uncommon to do just that with msignal.

>> 3. The tink_core library in itself is entirely macro free and declares
>> primitives and the most common operations on them. Promhx comes with a
>> number of convenience macros and adapters for JavaScript/jQuery stuff.
>> I am working to add some of these things in other parts of Tinkerbell,
>> but to me it was important to keep the core lightweight and small.
>
> Yep, all of these concepts are somewhat new. I wanted to provide a
> "batteries included" version so that you can immediately start using these
> techniques with more established frameworks, etc.

I think a separate promhx_jquery and promhx_http would be a better
idea. So that different parts of the library can move at different
speed and so forth. But that's your prerogative ;)

>> 4. In tink_core the possibility of errors is expressed through
>> Outcome. That's because I feel it's important not to blur the line
>> between systematic failure (e.g. I/O doesn't work because the real
>> world is a gruesome place) and human failure (e.g. code that causes a
>> null pointer exception), commonly distinguished as errors and
>> exceptions. This makes errors visible and first class. So in tink_core
>> I can distinguish `Future<Result>` and `Future<Outcome<Result,
>> Error>>` (or `Surprise<Result, Error>` as I like to alias it).
>> The latter can fail. But if I happen to have an error recovery
>> function `Error->Result` I can turn the latter to the former and
>> express that through the type system. As opposed to that, Promhx
>> treats errors and exceptions the same way and therefore you've got
>> this separate untyped mechanism for error handling. It also has ways
>> to recover errors, but they elude the type system.
>
> Promhx has special error handling, since I think dealing with errors in
> async code ranges from tedious to unpredictable. You can still treat
> certain errors as first class values (via a wrapped Outcome<T> or similar),
> but promhx provides a totally separate mechanism for handling "everything
> else".
> For instance, "catchError()" will function like a "catch" directive applied
> to the promise chain up until that point. "errorThen" will function like a
> "then", but will map any errors back to useful values, so that the rest of
> the chain can still work from there. It's more of a safety net.

Sure, but going untyped is an unnecessary sacrifice.
You can look at how the `>>` operator allows to propagate failures
silently but also to deal with them at any point:
https://github.com/haxetink/tink_core#operators

That being said, this probably more of an issue of belief ;)

Regards,
Juraj

Justin Donaldson

unread,
Apr 28, 2014, 3:07:06 AM4/28/14
to Haxe

More details,  TL;DR: I've provided an updated version of promhx that addresses some of this.


>> Quite frankly I wasn't really aware of promhx when I started out. But
>> since I had carte blanche, there are quite a few differences.
>>
>> 1. Futures and Signals are readonly. You have to use a SignalTrigger
>> and a FutureTrigger to be able to write data (i.e. resolve the
>> promise). This is similar to Scala, where a Future is a readonly
>> Promise. I considered this important, given that you may not want to
>> expose write access to client code.
>
>
> Yes, promhx provides much simpler wrapper in that sense.  I suppose that's
> something I could provide read only versions eventually.

Any kind of composability can be achieved on the read only versions,
so I'm not sure why it is coupled with writability.

To give you my honest opinion: I think this is actually vital. When I
revisited tink_core I considered to build on promhx. The error
handling I could have lived with, but this for me was a no-go.

Fair enough.  I think it can be added, but will break a lot of code.  I'll look into it for the next version.
 

>> 2. In Promhx you cannot unregister handlers. This is in accordance to
>> the standard. But particularly for signals/streams, this is
>> impractical.
>
> That's something I'm working on improving still.  However, there's a
> "pause()" method for streams that will momentarily stop the stream, and an
> "end()" method that is permanent.

This is not at all the same. Say a button has a click stream and you
attach a listener and want to detach it again:

typedef Button = {
    var clicked(default, null):Stream<MouseEvent>;
}

You don't want to pause that stream and affect any other listeners
that may be registered (now or in the future).
The approach is ok if you create the click stream on the fly (as with
your jquery thingy), but you can't do the above, which I think is a
pitty, because it makes for a nice type safe interface and it's not
uncommon to do just that with msignal.


I just added a "detachStream" method that will break the downstream link between two directly connected streams.  



I think a separate promhx_jquery and promhx_http would be a better
idea. So that different parts of the library can move at different
speed and so forth. But that's your prerogative ;)

It's a tiny bit of code for now, so I'll leave it just a bit longer.  But, it's a good idea in general.
 

>  Promhx has special error handling, since I think dealing with errors in
> async code ranges from tedious to unpredictable.  You can still treat
> certain errors as first class values (via a wrapped Outcome<T> or similar),
> but promhx provides a totally separate mechanism for handling "everything
> else".
> For instance, "catchError()" will function like a "catch" directive applied
> to the promise chain up until that point.  "errorThen" will function like a
> "then", but will map any errors back to useful values, so that the rest of
> the chain can still work from there.  It's more of a safety net.

Sure, but going untyped is an unnecessary sacrifice.

I disagree there... I don't see how it sacrifices anything at all.  What am I missing?   You can still use type parameters and wrappers for errors if you want to capture that level of detail.
 
You can look at how the `>>` operator allows to propagate failures
silently but also to deal with them at any point:
https://github.com/haxetink/tink_core#operators

That is pretty clever...  although it's not fair you criticize promhx for having a few macros, while you have defined an entire abstract boolean operator logic for Futures and Surprises :)

-Justin

Juraj Kirchheim

unread,
Apr 28, 2014, 8:02:27 AM4/28/14
to haxe...@googlegroups.com
On Mon, Apr 28, 2014 at 9:07 AM, Justin Donaldson <jdona...@gmail.com> wrote:
>
> More details, TL;DR: I've provided an updated version of promhx that
> addresses some of this.

Excellent ;)

[...]
>> Any kind of composability can be achieved on the read only versions,
>> so I'm not sure why it is coupled with writability.
>>
>> To give you my honest opinion: I think this is actually vital. When I
>> revisited tink_core I considered to build on promhx. The error
>> handling I could have lived with, but this for me was a no-go.
>
> Fair enough. I think it can be added, but will break a lot of code. I'll
> look into it for the next version.
.
That is the price of progress ... :D

>> >> 2. In Promhx you cannot unregister handlers. This is in accordance to
>> >> the standard. But particularly for signals/streams, this is
>> >> impractical.
[...]

> I just added a "detachStream" method that will break the downstream link
> between two directly connected streams.

Now we're talking!

>> I think a separate promhx_jquery and promhx_http would be a better
>> idea. So that different parts of the library can move at different
>> speed and so forth. But that's your prerogative ;)
>
> It's a tiny bit of code for now, so I'll leave it just a bit longer. But,
> it's a good idea in general.

Despite me maintaining a separate effort, I do hope for promhx to
grow, so I'm looking forward to when it's more than that tiny bit ;)
Wheel reinvention is an issue in our community. But I believe if we
all try to make smaller wheels, things become easier to combine and
the duplication goes down and the diversity becomes a factor for
collaboration rather than isolation.

>> > Promhx has special error handling, since I think dealing with errors in
>> > async code ranges from tedious to unpredictable.
[...]
>> Sure, but going untyped is an unnecessary sacrifice.
>
> I disagree there... I don't see how it sacrifices anything at all. What am
> I missing? You can still use type parameters and wrappers for errors if
> you want to capture that level of detail.

You can do that. Fair enough. It just seems that promhx promotes the
opposite and then that's what people are going to do. And when some
3rd party library yields a Promise, I will have to look at
documentation or implementation to know what kinds of errors to
expect. A good example of that would be promhx.haxe.Http. How do I
know there's an error (ok, that would be common sense) and what its
type is? Ultimately I have to dig as deep as haxe.Http.onError to know
it's going to be a String.

Say you had this:

typedef Http = {
function load(url:String):Surprise<String, Error>;
}

It becomes very clear, that there is an error to be expected. And what
kind of thing it's going to be.

And now, I can recover from the error:

var cfg = http.load('path/to/config.json').map(function (o) return switch o {
case Success(data): data;
default: haxe.Resource.getString('default_config');
});

$type(cfg);//Future<String>

Which formalizes the fact, that the error has actually been dealt
with. It becomes statically checkable. Sure, with such a trivial
example it doesn't seem like that much of a deal. But in more complex
systems it is very useful for errors to surface. If one component
introduces a new kind of error that its consumer is not able to deal
with yet, the compiler will tell you. Which I think is *very* helpful.

The point can be made that this is too explicit and thus too noisy,
but I would say the noise of errors is already there, so it should be
visible to us as programmers, rather than hidden to us and visible to
our users in the form of crashes or useless error messages.

>> You can look at how the `>>` operator allows to propagate failures
>> silently but also to deal with them at any point:
>> https://github.com/haxetink/tink_core#operators
>
> That is pretty clever... although it's not fair you criticize promhx for
> having a few macros, while you have defined an entire abstract boolean
> operator logic for Futures and Surprises :)

I'm not against "advanced" features (if that's what you think I
meant). Yes, they sometimes shoot you in the foot real bad, but they
are there for a reason and are constantly improving. And I like to use
them - probably a bit too much in fact. But the critical problem with
macros is the relatively bad macro-in-macro support that makes code
untyped and causes the compiler server not to cache and things like
that.

For me it is important for tink_core to work flawlessly in the macro
context, precisely because I like macros very much and I think we
should not waste the chance to bring this kind of stuff to macros as
well. I wasn't really concerned with the monax stuff, which is really
a nice addon, but with Promise.when, which seems key to composition.
But it did occur to me, that one can basically use pipe to get the
same thing by hand (which is quite tedious but possible) so I withdraw
my objection ;)

Regards,
Juraj

Justin Donaldson

unread,
May 20, 2014, 11:30:46 PM5/20/14
to Haxe

>> Any kind of composability can be achieved on the read only versions,
>> so I'm not sure why it is coupled with writability.
>>
>> To give you my honest opinion: I think this is actually vital. When I
>> revisited tink_core I considered to build on promhx. The error
>> handling I could have lived with, but this for me was a no-go.
>
> Fair enough. I think it can be added, but will break a lot of code.  I'll
> look into it for the next version.
.
That is the price of progress ... :D

I just pushed version 1.0 of promhx, which has support for read-only promise and streams.  There's also a "PublicStream" which follows the original public writable interface.

I've also dropped the "update" method for streams, since the interface is now primarily on Deferred.

 
Best,
-Justin
Reply all
Reply to author
Forward
0 new messages