:- use_module(library(spawn)).

delayed_unify(X,Y) :-
    sleep(1),
    X = Y.

one_solution(foo(bar)).

two_solutions(thing(one)).
two_solutions(thing(two)).

exceptional(_) :-
    throw(oh_no).

fails(_) :-
    fail.

thread_count(N) :-
    bagof(Id,S^thread_property(Id,status(S)),Ids),
    length(Ids,N).

:- use_module(library(tap)).

% two spawned goals actually happen in parallel
goes_faster :-
    get_time(Start),
    spawn(delayed_unify(A,a)),
    spawn(delayed_unify(B,b)),
    get_time(Mid),

    % getting here takes way less than one second
    % so these should both still be variables
    var(A),
    var(B),

    % wait for the computations to finish
    A = a,
    B = b,
    get_time(End),

    % make sure the timings are reasonable
    Mid - Start < 0.1, % async/1 returns quickly
    End - Start < 1.1, % sleep calls happened in parallel
    End - Start > 0.9. % sleep calls actually happened


% unification with a single solution leaves no choicepoints
single :-
    spawn(one_solution(X)),
    X = foo(Y),
    Y == bar.


% unification with first or second solution is OK
double_first :-
    spawn(two_solutions(X)),
    X = thing(N),
    N == one,
    !.  % trim choicepoint representing thing(two)

double_second :-
    spawn(two_solutions(X)),
    X = thing(N),
    N == two.


% unification fails when there are no solutions
zero(fail) :-
    spawn(fails(X)),
    X = never_will_suceed.


% unification rethrows its goal's exception
an_exception(throws(oh_no)) :-
    spawn(exceptional(X)),
    X = never_will_suceed.


% make sure we don't leave threads lying around
no_leftover_threads :-
    thread_count(1),  % only the main thread running
    spawn(one_solution(X)),
    X = foo(bar),
    thread_count(1).  % intermediate threads have been reclaimed


appending(todo("See https://github.com/mndrix/spawn/issues/1")) :-
    spawn(atom_codes(hello,Hello)),
    append(Hello,` world`, Message),
    Message = `hello world`.
