From 352ddeb475ab48aabfab59558827e0e8d927551f Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Thu, 4 Jun 2015 17:00:16 -0400 Subject: [PATCH 01/16] Implement hope_option:validate/2 --- src/hope.app.src | 2 +- src/hope_option.erl | 11 +++++++++++ test/hope_option_SUITE.erl | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/hope.app.src b/src/hope.app.src index 45b460d..41b038c 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.3.0"}, + {vsn, "3.4.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_option.erl b/src/hope_option.erl index 02ef2f3..996e342 100644 --- a/src/hope_option.erl +++ b/src/hope_option.erl @@ -19,6 +19,7 @@ , iter/2 , of_result/1 , of_undefined/1 + , validate/2 ]). @@ -80,3 +81,13 @@ of_result({error, _}) -> none. t(A). of_undefined(undefined) -> none; of_undefined(X) -> {some, X}. + +-spec validate(t(A), fun((A) -> boolean())) -> + t(A). +validate(none, _) -> + none; +validate({some, X}=T, F) -> + case F(X) + of false -> none + ; true -> T + end. diff --git a/test/hope_option_SUITE.erl b/test/hope_option_SUITE.erl index 370cf24..af1e12e 100644 --- a/test/hope_option_SUITE.erl +++ b/test/hope_option_SUITE.erl @@ -15,6 +15,7 @@ , t_map/1 , t_iter/1 , t_pipe/1 + , t_validate/1 ]). @@ -38,6 +39,7 @@ groups() -> , t_map , t_iter , t_pipe + , t_validate ], Properties = [parallel], [ {?GROUP, Properties, Tests} @@ -99,3 +101,9 @@ t_of_undefined(_Cfg) -> {some, Bar} = hope_option:of_undefined(Bar), {some, Baz} = hope_option:of_undefined(Baz), none = hope_option:of_undefined(undefined). + +t_validate(_Cfg) -> + IsFoo = fun (X) -> X =:= foo end, + none = hope_option:validate(none, IsFoo), + none = hope_option:validate({some, bar}, IsFoo), + {some, foo} = hope_option:validate({some, foo}, IsFoo). -- 2.20.1 From e0fbc1da088f6d4a87c06c80ad40e2d40339b12a Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Thu, 4 Jun 2015 18:19:48 -0400 Subject: [PATCH 02/16] Add dictionary get with validation. --- src/hope.app.src | 2 +- src/hope_gen_dictionary.erl | 3 +++ src/hope_kv_list.erl | 12 ++++++++++-- test/hope_dictionary_SUITE.erl | 4 +++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/hope.app.src b/src/hope.app.src index 41b038c..98c9de0 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.4.0"}, + {vsn, "3.5.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_gen_dictionary.erl b/src/hope_gen_dictionary.erl index dda6eb0..1fab838 100644 --- a/src/hope_gen_dictionary.erl +++ b/src/hope_gen_dictionary.erl @@ -18,6 +18,9 @@ -callback get(t(K, V), K, V) -> V. +-callback get(t(K, V), K, V, fun((V) -> boolean())) -> + V. + -callback set(t(K, V), K, V) -> t(K, V). diff --git a/src/hope_kv_list.erl b/src/hope_kv_list.erl index 7b25797..cb1c4a4 100644 --- a/src/hope_kv_list.erl +++ b/src/hope_kv_list.erl @@ -13,8 +13,9 @@ -export( [ empty/0 - , get/2 - , get/3 + , get/2 % get option + , get/3 % get existing or default + , get/4 % get existing if valid, or default , set/3 , update/3 , pop/2 @@ -75,6 +76,13 @@ get(T, K, Default) -> Vopt = get(T, K), hope_option:get(Vopt, Default). +-spec get(t(K, V), K, V, fun((V) -> boolean())) -> + V. +get(T, K, Default, IsValid) -> + VOpt1 = get(T, K), + VOpt2 = hope_option:validate(VOpt1, IsValid), + hope_option:get(VOpt2, Default). + -spec set(t(K, V), K, V) -> t(K, V). set(T, K, V) -> diff --git a/test/hope_dictionary_SUITE.erl b/test/hope_dictionary_SUITE.erl index 4f355ca..04e40d3 100644 --- a/test/hope_dictionary_SUITE.erl +++ b/test/hope_dictionary_SUITE.erl @@ -63,7 +63,9 @@ t_get(Cfg) -> {some, V1} = DictModule:get(D, K1), V1 = DictModule:get(D, K1, V2), none = DictModule:get(D, K2), - V2 = DictModule:get(D, K2, V2). + V2 = DictModule:get(D, K2, V2), + default = DictModule:get(D, K1, default, fun (X) -> X =:= foo end), + V1 = DictModule:get(D, K1, default, fun (X) -> X =:= V1 end). t_set_new(Cfg) -> {some, DictModule} = hope_kv_list:get(Cfg, ?DICT_MODULE), -- 2.20.1 From 67535be2404f057f0df3e128c24b484f066996a4 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Wed, 1 Jul 2015 14:37:54 -0400 Subject: [PATCH 03/16] Add has_key/2 dictionary method. --- src/hope.app.src | 2 +- src/hope_gen_dictionary.erl | 3 +++ src/hope_kv_list.erl | 5 +++++ test/hope_dictionary_SUITE.erl | 12 ++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/hope.app.src b/src/hope.app.src index 98c9de0..d2b376c 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.5.0"}, + {vsn, "3.6.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_gen_dictionary.erl b/src/hope_gen_dictionary.erl index 1fab838..ea0f551 100644 --- a/src/hope_gen_dictionary.erl +++ b/src/hope_gen_dictionary.erl @@ -48,3 +48,6 @@ -callback to_kv_list(t(K, V)) -> [{K, V}]. + +-callback has_key(t(K, _), K) -> + boolean(). diff --git a/src/hope_kv_list.erl b/src/hope_kv_list.erl index cb1c4a4..c12f3b4 100644 --- a/src/hope_kv_list.erl +++ b/src/hope_kv_list.erl @@ -25,6 +25,7 @@ , fold/3 , of_kv_list/1 , to_kv_list/1 + , has_key/2 , find_unique_presence_violations/2 % No optional keys , find_unique_presence_violations/3 % Specify optional keys , validate_unique_presence/2 % No optional keys @@ -206,6 +207,10 @@ presence_violations_to_list(#hope_kv_list_presence_violations end, ErrorDups ++ ErrorMissing ++ ErrorUnsupported. +-spec has_key(t(K, _), K) -> + boolean(). +has_key(T, K1) -> + lists:any(fun ({K2, _}) -> K1 =:= K2 end, T). %% ============================================================================ %% Helpers diff --git a/test/hope_dictionary_SUITE.erl b/test/hope_dictionary_SUITE.erl index 04e40d3..cf0bfba 100644 --- a/test/hope_dictionary_SUITE.erl +++ b/test/hope_dictionary_SUITE.erl @@ -16,6 +16,7 @@ , t_pop/1 , t_fold/1 , t_dictionary_specs/1 + , t_has_key/1 ]). @@ -38,6 +39,7 @@ groups() -> , t_pop , t_fold , t_dictionary_specs + , t_has_key ], Properties = [parallel], [{?DICT_MODULE_KV_LIST, Properties, Tests}]. @@ -107,3 +109,13 @@ t_fold(Cfg) -> t_dictionary_specs(Cfg) -> {some, DictModule} = hope_kv_list:get(Cfg, ?DICT_MODULE), [] = proper:check_specs(DictModule). + +t_has_key(Cfg) -> + {some, DictModule} = hope_kv_list:get(Cfg, ?DICT_MODULE), + D = DictModule:of_kv_list([{a, 1}, {b, 2}, {c, 3}]), + true = DictModule:has_key(D, a), + true = DictModule:has_key(D, b), + true = DictModule:has_key(D, c), + false = DictModule:has_key(D, d), + false = DictModule:has_key(D, e), + false = DictModule:has_key(D, f). -- 2.20.1 From 54ab0c82897759fb29293b9b872f444dc1408dab Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Thu, 30 Jul 2015 00:35:31 -0400 Subject: [PATCH 04/16] Add hope_list:map_result/2 --- src/hope.app.src | 2 +- src/hope_list.erl | 18 +++++++++++++++++- test/hope_list_SUITE.erl | 12 ++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/hope.app.src b/src/hope.app.src index d2b376c..c5c860f 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.6.0"}, + {vsn, "3.7.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_list.erl b/src/hope_list.erl index f179d1a..63d9c99 100644 --- a/src/hope_list.erl +++ b/src/hope_list.erl @@ -10,6 +10,7 @@ , map/3 % Tunable recursion limit , map_rev/2 , map_slow/2 + , map_result/2 % Not tail-recursive , first_match/2 ]). @@ -66,7 +67,6 @@ map([X1, X2, X3, X4, X5 | Xs], F, RecursionLimit, RecursionCount) -> end, [Y1, Y2, Y3, Y4, Y5 | Ys]. - %% @doc lists:reverse(map_rev(L, F)) %% @end -spec map_slow([A], fun((A) -> (B))) -> @@ -91,6 +91,22 @@ map_rev_acc([X|Xs], F, Ys) -> Y = F(X), map_rev_acc(Xs, F, [Y|Ys]). +-spec map_result([A], fun((A) -> (hope_result:t(B, C)))) -> + hope_result:t([B], C). +map_result([], _) -> + {ok, []}; +map_result([X | Xs], F) -> + case F(X) + of {ok, Y} -> + case map_result(Xs, F) + of {ok, Ys} -> + {ok, [Y | Ys]} + ; {error, _}=Error -> + Error + end + ; {error, _}=Error -> + Error + end. -spec unique_preserve_order(t(A)) -> t(A). diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 9557aab..7152503 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -16,6 +16,7 @@ , t_map_slow/1 , t_map/1 , t_map_3/1 + , t_map_result/1 ]). @@ -39,6 +40,7 @@ groups() -> , t_map_slow , t_map , t_map_3 + , t_map_result ], Properties = [parallel], [{?GROUP, Properties, Tests}]. @@ -89,3 +91,13 @@ prop_unique_preserve_order() -> t_hope_list_specs(_) -> [] = proper:check_specs(hope_list). + +t_map_result(_Cfg) -> + AssertPositive = + fun (I) when I > 0 -> {ok, I}; (_) -> {error, negative} end, + AllPositives = lists:seq(1, 5), + AllNegatives = lists:seq(-5, -1), + Mixed = lists:seq(-5, 5), + {ok, AllPositives} = hope_list:map_result(AllPositives, AssertPositive), + {error, negative} = hope_list:map_result(AllNegatives, AssertPositive), + {error, negative} = hope_list:map_result(Mixed, AssertPositive). -- 2.20.1 From fd66fd0c607571374fd2c4a5cd3131b042bded74 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 16:31:15 -0700 Subject: [PATCH 05/16] Do not import unused PropEr bindings --- test/hope_list_SUITE.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 7152503..1919249 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -1,6 +1,6 @@ -module(hope_list_SUITE). --include_lib("proper/include/proper.hrl"). +-include_lib("proper/include/proper_common.hrl"). %% Callbacks -export( @@ -54,35 +54,35 @@ t_map_rev(_Cfg) -> ?PROPTEST(map_rev). map_rev() -> - ?FORALL({L, F}, {list(integer()), function([integer()], term())}, + ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L))). t_map_slow(_Cfg) -> ?PROPTEST(map_slow). map_slow() -> - ?FORALL({L, F}, {list(integer()), function([integer()], term())}, + ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, hope_list:map_slow(L, F) == lists:map(F, L)). t_map(_Cfg) -> ?PROPTEST(map). map() -> - ?FORALL({L, F}, {list(integer()), function([integer()], term())}, + ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, hope_list:map(L, F) == lists:map(F, L)). t_map_3(_Cfg) -> ?PROPTEST(map_3). map_3() -> - ?FORALL({L, F, N}, {list(integer()), function([integer()], term()), non_neg_integer()}, + ?FORALL({L, F, N}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term()), proper_types:non_neg_integer()}, hope_list:map(L, F, N) == lists:map(F, L)). t_unique_preserve_order(_Cfg) -> ?PROPTEST(prop_unique_preserve_order). prop_unique_preserve_order() -> - ?FORALL(L, list(), + ?FORALL(L, proper_types:list(), begin Duplicates = L -- lists:usort(L), hope_list:unique_preserve_order(L) == -- 2.20.1 From ea7f00f1c137112f5def73f54244f79c7336363a Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 16:39:31 -0700 Subject: [PATCH 06/16] Abbreviate proper_types module. --- test/hope_list_SUITE.erl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 1919249..8994868 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -24,6 +24,8 @@ -define(PROPTEST(A), true = proper:quickcheck(A())). +-define(type, proper_types). + %% ============================================================================ %% Common Test callbacks @@ -54,35 +56,35 @@ t_map_rev(_Cfg) -> ?PROPTEST(map_rev). map_rev() -> - ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, + ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L))). t_map_slow(_Cfg) -> ?PROPTEST(map_slow). map_slow() -> - ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, + ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, hope_list:map_slow(L, F) == lists:map(F, L)). t_map(_Cfg) -> ?PROPTEST(map). map() -> - ?FORALL({L, F}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term())}, + ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, hope_list:map(L, F) == lists:map(F, L)). t_map_3(_Cfg) -> ?PROPTEST(map_3). map_3() -> - ?FORALL({L, F, N}, {proper_types:list(proper_types:integer()), proper_types:function([proper_types:integer()], proper_types:term()), proper_types:non_neg_integer()}, + ?FORALL({L, F, N}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term()), ?type:non_neg_integer()}, hope_list:map(L, F, N) == lists:map(F, L)). t_unique_preserve_order(_Cfg) -> ?PROPTEST(prop_unique_preserve_order). prop_unique_preserve_order() -> - ?FORALL(L, proper_types:list(), + ?FORALL(L, ?type:list(), begin Duplicates = L -- lists:usort(L), hope_list:unique_preserve_order(L) == -- 2.20.1 From cf0f905c6b980352e35a8069c4a1a8680803e27d Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 17:14:32 -0700 Subject: [PATCH 07/16] Re-organize hope_list_SUITE --- test/hope_list_SUITE.erl | 76 ++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 8994868..d5ac392 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -22,7 +22,7 @@ -define(GROUP , hope_list). --define(PROPTEST(A), true = proper:quickcheck(A())). +-define(CHECK(F), true = proper:quickcheck(F())). -define(type, proper_types). @@ -53,45 +53,21 @@ groups() -> %% ============================================================================= t_map_rev(_Cfg) -> - ?PROPTEST(map_rev). - -map_rev() -> - ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, - hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L))). + ?CHECK(proper_spec_map_rev). t_map_slow(_Cfg) -> - ?PROPTEST(map_slow). - -map_slow() -> - ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, - hope_list:map_slow(L, F) == lists:map(F, L)). + ?CHECK(proper_spec_map_slow). t_map(_Cfg) -> - ?PROPTEST(map). - -map() -> - ?FORALL({L, F}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term())}, - hope_list:map(L, F) == lists:map(F, L)). + ?CHECK(proper_spec_map). t_map_3(_Cfg) -> - ?PROPTEST(map_3). - -map_3() -> - ?FORALL({L, F, N}, {?type:list(?type:integer()), ?type:function([?type:integer()], ?type:term()), ?type:non_neg_integer()}, - hope_list:map(L, F, N) == lists:map(F, L)). + ?CHECK(proper_spec_map_3). t_unique_preserve_order(_Cfg) -> - ?PROPTEST(prop_unique_preserve_order). + ?CHECK(proper_spec_prop_unique_preserve_order). -prop_unique_preserve_order() -> - ?FORALL(L, ?type:list(), - begin - Duplicates = L -- lists:usort(L), - hope_list:unique_preserve_order(L) == - lists:reverse(lists:reverse(L) -- Duplicates) - end). - -t_hope_list_specs(_) -> +t_hope_list_specs(_Cfg) -> [] = proper:check_specs(hope_list). t_map_result(_Cfg) -> @@ -103,3 +79,41 @@ t_map_result(_Cfg) -> {ok, AllPositives} = hope_list:map_result(AllPositives, AssertPositive), {error, negative} = hope_list:map_result(AllNegatives, AssertPositive), {error, negative} = hope_list:map_result(Mixed, AssertPositive). + +%% ============================================================================ +%% PropEr test specs +%% ============================================================================ + +proper_spec_map_rev() -> + ?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L)) + ). + +proper_spec_map_slow() -> + ?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map_slow(L, F) == lists:map(F, L) + ). + +proper_spec_map() -> + ?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map(L, F) == lists:map(F, L) + ). + +proper_spec_map_3() -> + ?FORALL({L, F, N}, {type_l(), type_f(), ?type:non_neg_integer()}, + hope_list:map(L, F, N) == lists:map(F, L) + ). + +proper_spec_prop_unique_preserve_order() -> + ?FORALL(L, ?type:list(), + begin + Duplicates = L -- lists:usort(L), + hope_list:unique_preserve_order(L) == + lists:reverse(lists:reverse(L) -- Duplicates) + end). + +type_l() -> + ?type:list(?type:integer()). + +type_f() -> + ?type:function([?type:integer()], ?type:term()). -- 2.20.1 From 2fa2f0266468d1b726aae16141671fed267eab53 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 17:39:18 -0700 Subject: [PATCH 08/16] Explicitly name generated (auto) and manual test cases --- test/hope_list_SUITE.erl | 65 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index d5ac392..8601cf9 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -10,13 +10,13 @@ %% Test cases -export( - [ t_unique_preserve_order/1 - , t_hope_list_specs/1 - , t_map_rev/1 - , t_map_slow/1 - , t_map/1 - , t_map_3/1 - , t_map_result/1 + [ t_auto_hope_list_specs/1 + , t_auto_map/1 + , t_auto_map_3/1 + , t_auto_map_rev/1 + , t_auto_map_slow/1 + , t_auto_unique_preserve_order/1 + , t_manual_map_result/1 ]). @@ -36,50 +36,53 @@ all() -> groups() -> Tests = - [ t_unique_preserve_order - , t_hope_list_specs - , t_map_rev - , t_map_slow - , t_map - , t_map_3 - , t_map_result + [ t_auto_hope_list_specs + , t_auto_map + , t_auto_map_3 + , t_auto_map_rev + , t_auto_map_slow + , t_auto_unique_preserve_order + , t_manual_map_result ], Properties = [parallel], [{?GROUP, Properties, Tests}]. +%% ============================================================================= +%% Manual test cases +%% ============================================================================= + +t_manual_map_result(_Cfg) -> + AssertPositive = + fun (I) when I > 0 -> {ok, I}; (_) -> {error, negative} end, + AllPositives = lists:seq(1, 5), + AllNegatives = lists:seq(-5, -1), + Mixed = lists:seq(-5, 5), + {ok, AllPositives} = hope_list:map_result(AllPositives, AssertPositive), + {error, negative} = hope_list:map_result(AllNegatives, AssertPositive), + {error, negative} = hope_list:map_result(Mixed, AssertPositive). %% ============================================================================= -%% Test cases +%% Generated test cases %% ============================================================================= -t_map_rev(_Cfg) -> +t_auto_map_rev(_Cfg) -> ?CHECK(proper_spec_map_rev). -t_map_slow(_Cfg) -> +t_auto_map_slow(_Cfg) -> ?CHECK(proper_spec_map_slow). -t_map(_Cfg) -> +t_auto_map(_Cfg) -> ?CHECK(proper_spec_map). -t_map_3(_Cfg) -> +t_auto_map_3(_Cfg) -> ?CHECK(proper_spec_map_3). -t_unique_preserve_order(_Cfg) -> +t_auto_unique_preserve_order(_Cfg) -> ?CHECK(proper_spec_prop_unique_preserve_order). -t_hope_list_specs(_Cfg) -> +t_auto_hope_list_specs(_Cfg) -> [] = proper:check_specs(hope_list). -t_map_result(_Cfg) -> - AssertPositive = - fun (I) when I > 0 -> {ok, I}; (_) -> {error, negative} end, - AllPositives = lists:seq(1, 5), - AllNegatives = lists:seq(-5, -1), - Mixed = lists:seq(-5, 5), - {ok, AllPositives} = hope_list:map_result(AllPositives, AssertPositive), - {error, negative} = hope_list:map_result(AllNegatives, AssertPositive), - {error, negative} = hope_list:map_result(Mixed, AssertPositive). - %% ============================================================================ %% PropEr test specs %% ============================================================================ -- 2.20.1 From d302b4a314bba4b3fde4d6c558af3037d4fcc25d Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 17:44:10 -0700 Subject: [PATCH 09/16] Bring-back manual test case for hope_list --- test/hope_list_SUITE.erl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 8601cf9..1311ba9 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -16,7 +16,10 @@ , t_auto_map_rev/1 , t_auto_map_slow/1 , t_auto_unique_preserve_order/1 + , t_manual_map/1 , t_manual_map_result/1 + , t_manual_map_rev/1 + , t_manual_map_slow/1 ]). @@ -42,7 +45,10 @@ groups() -> , t_auto_map_rev , t_auto_map_slow , t_auto_unique_preserve_order + , t_manual_map , t_manual_map_result + , t_manual_map_rev + , t_manual_map_slow ], Properties = [parallel], [{?GROUP, Properties, Tests}]. @@ -51,6 +57,13 @@ groups() -> %% Manual test cases %% ============================================================================= +t_manual_map(_Cfg) -> + F = fun (N) -> N + 1 end, + Xs = lists:seq(1, 5010), + Ys = lists:map(F, Xs), + Ys = hope_list:map(Xs, F), + [] = hope_list:map([], F). + t_manual_map_result(_Cfg) -> AssertPositive = fun (I) when I > 0 -> {ok, I}; (_) -> {error, negative} end, @@ -61,6 +74,16 @@ t_manual_map_result(_Cfg) -> {error, negative} = hope_list:map_result(AllNegatives, AssertPositive), {error, negative} = hope_list:map_result(Mixed, AssertPositive). +t_manual_map_rev(_Cfg) -> + F = fun (N) -> N + 1 end, + [4, 3, 2] = hope_list:map_rev([1, 2, 3], F), + [] = hope_list:map_rev([], F). + +t_manual_map_slow(_Cfg) -> + F = fun (N) -> N + 1 end, + [2, 3, 4] = hope_list:map_slow([1, 2, 3], F), + [] = hope_list:map_slow([], F). + %% ============================================================================= %% Generated test cases %% ============================================================================= -- 2.20.1 From dbe73b30584bf349d97eb2caa9801a1a1f642564 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 5 Sep 2015 20:32:27 -0700 Subject: [PATCH 10/16] Remove superfluous indirection. --- test/hope_list_SUITE.erl | 55 +++++++++++++++------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 1311ba9..29b1b38 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -25,7 +25,7 @@ -define(GROUP , hope_list). --define(CHECK(F), true = proper:quickcheck(F())). +-define(TEST(TestSpec), true = proper:quickcheck(TestSpec)). -define(type, proper_types). @@ -89,55 +89,40 @@ t_manual_map_slow(_Cfg) -> %% ============================================================================= t_auto_map_rev(_Cfg) -> - ?CHECK(proper_spec_map_rev). + ?TEST(?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L)) + )). t_auto_map_slow(_Cfg) -> - ?CHECK(proper_spec_map_slow). + ?TEST(?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map_slow(L, F) == lists:map(F, L) + )). t_auto_map(_Cfg) -> - ?CHECK(proper_spec_map). + ?TEST(?FORALL({L, F}, {type_l(), type_f()}, + hope_list:map(L, F) == lists:map(F, L) + )). t_auto_map_3(_Cfg) -> - ?CHECK(proper_spec_map_3). + ?TEST(?FORALL({L, F, N}, {type_l(), type_f(), ?type:non_neg_integer()}, + hope_list:map(L, F, N) == lists:map(F, L) + )). t_auto_unique_preserve_order(_Cfg) -> - ?CHECK(proper_spec_prop_unique_preserve_order). + ?TEST(?FORALL(L, ?type:list(), + begin + Duplicates = L -- lists:usort(L), + hope_list:unique_preserve_order(L) == + lists:reverse(lists:reverse(L) -- Duplicates) + end)). t_auto_hope_list_specs(_Cfg) -> [] = proper:check_specs(hope_list). %% ============================================================================ -%% PropEr test specs +%% Common types %% ============================================================================ -proper_spec_map_rev() -> - ?FORALL({L, F}, {type_l(), type_f()}, - hope_list:map_rev(L, F) == lists:reverse(lists:map(F, L)) - ). - -proper_spec_map_slow() -> - ?FORALL({L, F}, {type_l(), type_f()}, - hope_list:map_slow(L, F) == lists:map(F, L) - ). - -proper_spec_map() -> - ?FORALL({L, F}, {type_l(), type_f()}, - hope_list:map(L, F) == lists:map(F, L) - ). - -proper_spec_map_3() -> - ?FORALL({L, F, N}, {type_l(), type_f(), ?type:non_neg_integer()}, - hope_list:map(L, F, N) == lists:map(F, L) - ). - -proper_spec_prop_unique_preserve_order() -> - ?FORALL(L, ?type:list(), - begin - Duplicates = L -- lists:usort(L), - hope_list:unique_preserve_order(L) == - lists:reverse(lists:reverse(L) -- Duplicates) - end). - type_l() -> ?type:list(?type:integer()). -- 2.20.1 From 347e63e68cda29d5fdcf5ffc0d551cc6d7a9bc6d Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sun, 6 Sep 2015 06:53:40 -0700 Subject: [PATCH 11/16] Explicitly name each version of the output under comparison. --- test/hope_list_SUITE.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index 29b1b38..e1f8375 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -112,8 +112,9 @@ t_auto_unique_preserve_order(_Cfg) -> ?TEST(?FORALL(L, ?type:list(), begin Duplicates = L -- lists:usort(L), - hope_list:unique_preserve_order(L) == - lists:reverse(lists:reverse(L) -- Duplicates) + UniquesInOrderA = lists:reverse(lists:reverse(L) -- Duplicates), + UniquesInOrderB = hope_list:unique_preserve_order(L), + UniquesInOrderA == UniquesInOrderB end)). t_auto_hope_list_specs(_Cfg) -> -- 2.20.1 From fcfc097ab895201de0cf21ebd89a8039b37bf25a Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 15 Sep 2015 16:49:27 -0400 Subject: [PATCH 12/16] Implement (and test) hope_list:divide/2 Divides list into sublists of up to a requested size + a remainder. Order unspecified. Size < 1 raises an error: hope_list__divide__size_must_be_a_positive_integer. --- src/hope.app.src | 2 +- src/hope_list.erl | 30 ++++++++++++++++++++++++++---- test/hope_list_SUITE.erl | 31 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/hope.app.src b/src/hope.app.src index c5c860f..91cd52f 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.7.0"}, + {vsn, "3.8.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_list.erl b/src/hope_list.erl index 63d9c99..b5edf42 100644 --- a/src/hope_list.erl +++ b/src/hope_list.erl @@ -12,16 +12,14 @@ , map_slow/2 , map_result/2 % Not tail-recursive , first_match/2 + , divide/2 ]). - -define(DEFAULT_RECURSION_LIMIT, 1000). - -type t(A) :: [A]. - %% @doc Tail-recursive equivalent of lists:map/2 %% @end -spec map([A], fun((A) -> (B))) -> @@ -74,7 +72,6 @@ map([X1, X2, X3, X4, X5 | Xs], F, RecursionLimit, RecursionCount) -> map_slow(Xs, F) -> lists:reverse(map_rev(Xs, F)). - %% @doc Tail-recursive alternative to lists:map/2, which accumulates and %% returns list in reverse order. %% @end @@ -129,3 +126,28 @@ first_match([{Tag, F} | Tests], X) -> of true -> {some, Tag} ; false -> first_match(Tests, X) end. + +%% @doc Divide list into sublists of up to a requested size + a remainder. +%% Order unspecified. Size < 1 raises an error: +%% `hope_list__divide__size_must_be_a_positive_integer' +%% @end +-spec divide([A], pos_integer()) -> + [[A]]. +divide(_, Size) when Size < 1 orelse not is_integer(Size) -> + % Q: Why? + % A: For N < 0, what does it mean to have a negative-sized chunk? + % For N = 0, we can imagine that a single chunk is an empty list, but, + % how many such chunks should we produce? + % This is pretty-much equivalnet to the problem of deviding something by 0. + error(hope_list__divide__size_must_be_a_positive_integer); +divide([], _) -> + []; +divide([X1 | Xs], MaxChunkSize) -> + MoveIntoChunks = + fun (X2, {Chunk, Chunks, ChunkSize}) when ChunkSize >= MaxChunkSize -> + {[X2], [Chunk | Chunks], 1} + ; (X2, {Chunk, Chunks, ChunkSize}) -> + {[X2 | Chunk], Chunks, ChunkSize + 1} + end, + {Chunk, Chunks, _} = lists:foldl(MoveIntoChunks, {[X1], [], 1}, Xs), + [Chunk | Chunks]. diff --git a/test/hope_list_SUITE.erl b/test/hope_list_SUITE.erl index e1f8375..386cdc6 100644 --- a/test/hope_list_SUITE.erl +++ b/test/hope_list_SUITE.erl @@ -20,6 +20,7 @@ , t_manual_map_result/1 , t_manual_map_rev/1 , t_manual_map_slow/1 + , t_manual_divide/1 ]). @@ -49,6 +50,7 @@ groups() -> , t_manual_map_result , t_manual_map_rev , t_manual_map_slow + , t_manual_divide ], Properties = [parallel], [{?GROUP, Properties, Tests}]. @@ -84,6 +86,35 @@ t_manual_map_slow(_Cfg) -> [2, 3, 4] = hope_list:map_slow([1, 2, 3], F), [] = hope_list:map_slow([], F). +t_manual_divide(_Cfg) -> + try + hope_list:divide([a, b, c], -1) + catch + error:hope_list__divide__size_must_be_a_positive_integer -> ok + end, + try + hope_list:divide([a, b, c], 0) + catch + error:hope_list__divide__size_must_be_a_positive_integer -> ok + end, + [[c], [b], [a]] = hope_list:divide([a, b, c], 1), + [[c], [b, a]] = hope_list:divide([a, b, c], 2), + [[c, b, a]] = hope_list:divide([a, b, c], 3), + [[c, b, a]] = hope_list:divide([a, b, c], 4), + [[c, b, a]] = hope_list:divide([a, b, c], 5), + try + hope_list:divide([], 0) + catch + error:hope_list__divide__size_must_be_a_positive_integer -> ok + end, + try + hope_list:divide([], -1) + catch + error:hope_list__divide__size_must_be_a_positive_integer -> ok + end, + [[f, e], [d, c], [b, a]] = hope_list:divide([a, b, c, d, e, f], 2), + [[f, e, d], [c, b, a]] = hope_list:divide([a, b, c, d, e, f], 3). + %% ============================================================================= %% Generated test cases %% ============================================================================= -- 2.20.1 From efcdece0c88c66e890be5724e707b9132c35df56 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 26 Sep 2015 13:19:48 -0400 Subject: [PATCH 13/16] feat: do not declare dependencies for non-test builds. --- Makefile | 12 +++++++----- rebar.config => rebar_test_build.config | 0 src/hope.app.src | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) rename rebar.config => rebar_test_build.config (100%) diff --git a/Makefile b/Makefile index 92e43ad..82b97a7 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +REBAR := rebar --config ./rebar_test_build.config + .PHONY: \ travis_ci \ fresh-build \ @@ -26,23 +28,23 @@ fresh-build: \ compile compile: - @rebar compile + @$(REBAR) compile clean: - @rebar clean + @$(REBAR) clean deps: \ deps-get \ deps-update deps-get: - @rebar get-deps + @$(REBAR) get-deps deps-update: - @rebar update-deps + @$(REBAR) update-deps dialyze: @dialyzer ebin test: - @rebar ct skip_deps=true --verbose=0 + @$(REBAR) ct skip_deps=true --verbose=0 diff --git a/rebar.config b/rebar_test_build.config similarity index 100% rename from rebar.config rename to rebar_test_build.config diff --git a/src/hope.app.src b/src/hope.app.src index 91cd52f..a399d58 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.8.0"}, + {vsn, "3.8.1"}, {registered, []}, {applications, [ kernel, -- 2.20.1 From d2bddc7bbf3325b16e1de0afabfebb6cf57b505e Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sat, 26 Sep 2015 15:00:04 -0400 Subject: [PATCH 14/16] Upgrade proper to current HEAD, to build on OTP 18.X Which causes one test (t_dictionary_specs) to fail for non-obvious reason - for now, disabled it and left a TODO note to fix later. --- .travis.yml | 2 ++ rebar_test_build.config | 2 +- test/hope_dictionary_SUITE.erl | 12 +++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c38a8fb..86fa0b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ language: erlang otp_release: + - 18.1 + - 18.0 - 17.0 - R16B02 diff --git a/rebar_test_build.config b/rebar_test_build.config index 8484333..801bc90 100644 --- a/rebar_test_build.config +++ b/rebar_test_build.config @@ -2,7 +2,7 @@ {clean_files, ["test/*.beam"]}. {deps, - [{proper, ".*", {git, "https://github.com/manopapad/proper.git", {tag, "v1.1"}}}] + [{proper, ".*", {git, "https://github.com/manopapad/proper.git", "20e62bc32f9bd43fe2ff52944a4ef99eb71d1399"}}] }. {cover_enabled, true}. diff --git a/test/hope_dictionary_SUITE.erl b/test/hope_dictionary_SUITE.erl index cf0bfba..c5bc8fe 100644 --- a/test/hope_dictionary_SUITE.erl +++ b/test/hope_dictionary_SUITE.erl @@ -38,7 +38,17 @@ groups() -> , t_get , t_pop , t_fold - , t_dictionary_specs + + % TODO: Find-out why t_dictionary_specs failes with latest proper HEAD: + % + % Testing hope_kv_list:to_kv_list/1 + % Error: The typeserver encountered an error: {unbound_var,'K'}. + % *** CT Error Notification 2015-09-26 13:46:38.684 *** + % hope_dictionary_SUITE:t_dictionary_specs failed on line 111 + % Reason: {badmatch,[{hope_kv_list,of_kv_list,1}]} + % + %, t_dictionary_specs + , t_has_key ], Properties = [parallel], -- 2.20.1 From 61cace5b6adc4e9385cda749448b12c81951eef1 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Sun, 4 Oct 2015 12:43:18 -0400 Subject: [PATCH 15/16] Upgrade proper to commit which fixes handling of user-defined types --- rebar_test_build.config | 2 +- test/hope_dictionary_SUITE.erl | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/rebar_test_build.config b/rebar_test_build.config index 801bc90..d3c1b34 100644 --- a/rebar_test_build.config +++ b/rebar_test_build.config @@ -2,7 +2,7 @@ {clean_files, ["test/*.beam"]}. {deps, - [{proper, ".*", {git, "https://github.com/manopapad/proper.git", "20e62bc32f9bd43fe2ff52944a4ef99eb71d1399"}}] + [{proper, ".*", {git, "https://github.com/manopapad/proper.git", "ced2ddb3a719aec22e5abe55c51b56658b0e8a87"}}] }. {cover_enabled, true}. diff --git a/test/hope_dictionary_SUITE.erl b/test/hope_dictionary_SUITE.erl index c5bc8fe..cf0bfba 100644 --- a/test/hope_dictionary_SUITE.erl +++ b/test/hope_dictionary_SUITE.erl @@ -38,17 +38,7 @@ groups() -> , t_get , t_pop , t_fold - - % TODO: Find-out why t_dictionary_specs failes with latest proper HEAD: - % - % Testing hope_kv_list:to_kv_list/1 - % Error: The typeserver encountered an error: {unbound_var,'K'}. - % *** CT Error Notification 2015-09-26 13:46:38.684 *** - % hope_dictionary_SUITE:t_dictionary_specs failed on line 111 - % Reason: {badmatch,[{hope_kv_list,of_kv_list,1}]} - % - %, t_dictionary_specs - + , t_dictionary_specs , t_has_key ], Properties = [parallel], -- 2.20.1 From 0ed4baebe135edc50d24d3613c6ee971f75d8c98 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Mon, 23 May 2016 14:10:26 -0400 Subject: [PATCH 16/16] Ignore outputs of side-effecting, element-handler functions. --- src/hope.app.src | 2 +- src/hope_gen_dictionary.erl | 2 +- src/hope_kv_list.erl | 2 +- src/hope_option.erl | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/hope.app.src b/src/hope.app.src index a399d58..d36cc38 100644 --- a/src/hope.app.src +++ b/src/hope.app.src @@ -1,7 +1,7 @@ {application, hope, [ {description, "Higher Order Programming in Erlang"}, - {vsn, "3.8.1"}, + {vsn, "3.9.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/hope_gen_dictionary.erl b/src/hope_gen_dictionary.erl index ea0f551..174ef67 100644 --- a/src/hope_gen_dictionary.erl +++ b/src/hope_gen_dictionary.erl @@ -39,7 +39,7 @@ -callback fold(t(K, V), fun((K, V, Acc) -> Acc), Acc) -> Acc. --callback iter(t(K, V), fun((K, V) -> ok)) -> +-callback iter(t(K, V), fun((K, V) -> any())) -> ok. %% TODO: Decide if validation is to be done. If yes - wrap in hope_result:t/1 diff --git a/src/hope_kv_list.erl b/src/hope_kv_list.erl index c12f3b4..44a78d4 100644 --- a/src/hope_kv_list.erl +++ b/src/hope_kv_list.erl @@ -105,7 +105,7 @@ pop(T1, K) -> ; false -> {none , T1} end. --spec iter(t(K, V), fun((K, V) -> ok)) -> +-spec iter(t(K, V), fun((K, V) -> any())) -> ok. iter(T, F1) -> F2 = lift_map(F1), diff --git a/src/hope_option.erl b/src/hope_option.erl index 996e342..c645c53 100644 --- a/src/hope_option.erl +++ b/src/hope_option.erl @@ -57,10 +57,13 @@ return(X, Condition) -> map({some, X}, F) -> {some, F(X)}; map(none , _) -> none. --spec iter(t(A), fun((A) -> (ok))) -> +-spec iter(t(A), fun((A) -> (any()))) -> + ok. +iter({some, X}, F) -> + _ = F(X), + ok; +iter(none, _) -> ok. -iter({some, X}, F) -> ok = F(X); -iter(none , _) -> ok. -spec pipe([fun((A) -> t(B))], A) -> t(B). -- 2.20.1