├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── state_bench.sh ├── include ├── pure_type.hrl └── state_type.hrl ├── priv └── .gitkeep ├── rebar.config ├── rebar.lock ├── src ├── async_iterate.erl ├── causal_context.erl ├── dot_fun.erl ├── dot_map.erl ├── dot_set.erl ├── dot_store.erl ├── lists_ext.erl ├── orddict_ext.erl ├── ordsets_ext.erl ├── pure_awset.erl ├── pure_dwflag.erl ├── pure_ewflag.erl ├── pure_gcounter.erl ├── pure_gset.erl ├── pure_mvregister.erl ├── pure_no_compaction.erl ├── pure_pncounter.erl ├── pure_polog.erl ├── pure_rwset.erl ├── pure_trcb.erl ├── pure_twopset.erl ├── pure_type.erl ├── state_awmap.erl ├── state_awset.erl ├── state_awset_ps.erl ├── state_bcounter.erl ├── state_boolean.erl ├── state_causal_type.erl ├── state_dwflag.erl ├── state_ewflag.erl ├── state_gcounter.erl ├── state_gmap.erl ├── state_gset.erl ├── state_ivar.erl ├── state_lexcounter.erl ├── state_lwwregister.erl ├── state_max_int.erl ├── state_mvmap.erl ├── state_mvregister.erl ├── state_orset.erl ├── state_pair.erl ├── state_pncounter.erl ├── state_twopset.erl ├── state_type.erl ├── type.erl ├── types.app.src ├── types.erl ├── types_app.erl └── types_sup.erl ├── test ├── prop_causal_context.erl ├── prop_dot_set.erl ├── prop_join_decompositions.erl ├── state_type_composability_SUITE.erl └── state_type_semantics_SUITE.erl └── tools.mk /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | .eunit 3 | deps/* 4 | ebin/* 5 | dev 6 | *.swp 7 | include/*.swp 8 | src/*.swp 9 | test/*.swp 10 | src/.DS_Store 11 | .DS_Store 12 | tags 13 | .qc/ 14 | riak_test/ebin/*.beam 15 | .eqc-info 16 | current_counterexample.eqc 17 | dialyzer_warnings 18 | .rebar/ 19 | distdir 20 | package 21 | deps.test/ 22 | rt/ 23 | .combo_dialyzer_plt 24 | build 25 | .vagrant 26 | _build/ 27 | TEST-* 28 | data/ 29 | *.deb 30 | log/ 31 | _checkouts/ 32 | priv/logs/*.csv 33 | priv/plots/*.pdf 34 | fprof.trace 35 | .rebar3/ 36 | .eqc/ 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 21.0.4 4 | - 20.3.8.2 5 | - 19.3 6 | - 18.3 7 | install: 8 | - wget https://s3.amazonaws.com/rebar3/rebar3 && chmod u+x rebar3 9 | - REBAR=./rebar3 make 10 | - ./rebar3 update 11 | script: 12 | - REBAR=./rebar3 make eunit 13 | - REBAR=./rebar3 make ct 14 | - REBAR=./rebar3 make proper 15 | - REBAR=./rebar3 make xref 16 | - REBAR=./rebar3 make dialyzer 17 | - REBAR=./rebar3 make lint 18 | notifications: 19 | email: christopher.meiklejohn@gmail.com 20 | slack: lasp-lang:hiPRNnbUa3zdGrrXZfGRAF7D 21 | irc: "irc.freenode.org#lasp-lang" 22 | sudo: false 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Christopher Meiklejohn 2 | All rights reserved. 3 | 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE ?= types 2 | VERSION ?= $(shell git describe --tags) 3 | BASE_DIR = $(shell pwd) 4 | ERLANG_BIN = $(shell dirname $(shell which erl)) 5 | REBAR ?= rebar3 6 | MAKE = make 7 | 8 | .PHONY: rel deps test eqc plots 9 | 10 | all: compile 11 | 12 | ## 13 | ## Compilation targets 14 | ## 15 | 16 | compile: 17 | $(REBAR) compile 18 | 19 | clean: packageclean 20 | $(REBAR) clean 21 | 22 | packageclean: 23 | rm -fr *.deb 24 | rm -fr *.tar.gz 25 | 26 | ## 27 | ## Test targets 28 | ## 29 | 30 | check: test xref dialyzer lint 31 | 32 | test: eunit ct proper 33 | 34 | eunit: 35 | ${REBAR} as test eunit 36 | 37 | ct: 38 | ${REBAR} as test ct 39 | 40 | proper: 41 | ${REBAR} as test proper 42 | 43 | lint: 44 | ${REBAR} as lint lint 45 | 46 | bench: compile 47 | bin/state_bench.sh 48 | 49 | shell: 50 | ${REBAR} shell --apps types 51 | 52 | include tools.mk 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Types 2 | ======================================================= 3 | 4 | [![Build Status](https://travis-ci.org/lasp-lang/types.svg?branch=master)](https://travis-ci.org/lasp-lang/types) 5 | 6 | ## Types 7 | 8 | Reference implementation for Conflict-free Replicated Data Types in 9 | Erlang. 10 | -------------------------------------------------------------------------------- /bin/state_bench.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | %%! -pa _build/default/lib/types/ebin/ 4 | 5 | main(_) -> 6 | benchmark(state_gset), 7 | benchmark(state_awset), 8 | ok. 9 | 10 | %% @private 11 | benchmark(Type) -> 12 | io:format("Running ~p~n", [Type]), 13 | R0 = create(Type), 14 | R1 = adds(Type, part(1), true, R0), 15 | R2 = adds(Type, part(2), false, R1), 16 | R3 = adds(Type, part(3), true, R2), 17 | analyze(R3). 18 | 19 | %% @private 20 | analyze({_, Metrics}) -> 21 | lists:foreach( 22 | fun({MetricType, Values}) -> 23 | Min = lists:min(Values), 24 | Max = lists:max(Values), 25 | Average = lists:sum(Values) / length(Values), 26 | 27 | io:format("- ~p:~n", [MetricType]), 28 | io:format(" > min: ~p~n", [Min]), 29 | io:format(" > max: ~p~n", [Max]), 30 | io:format(" > avg: ~p~n", [Average]) 31 | end, 32 | Metrics 33 | ). 34 | 35 | %% @private 36 | create(Type) -> 37 | lists:foldl( 38 | fun(Replica, {StateIn, MetricsIn}) -> 39 | {Time, Result} = timer:tc(fun() -> Type:new() end), 40 | StateOut = orddict:store(Replica, Result, StateIn), 41 | MetricsOut = orddict:append(new, 42 | Time, 43 | MetricsIn), 44 | {StateOut, MetricsOut} 45 | end, 46 | {orddict:new(), orddict:new()}, 47 | replicas() 48 | ). 49 | 50 | %% @private 51 | adds(Type, Seq, WithSync, R0) -> 52 | lists:foldl( 53 | fun(I, R1) -> 54 | %% each replica adds an element to the set 55 | R2 = lists:foldl( 56 | fun(Replica, {StateIn, MetricsIn}) -> 57 | Value = orddict:fetch(Replica, StateIn), 58 | Element = create_element(Replica, I), 59 | 60 | {Time, Result} = timer:tc( 61 | fun() -> 62 | {ok, NewValue} = Type:mutate({add, Element}, Replica, Value), 63 | NewValue 64 | end 65 | ), 66 | 67 | StateOut = orddict:store(Replica, Result, StateIn), 68 | MetricsOut = orddict:append(mutate, Time, MetricsIn), 69 | {StateOut, MetricsOut} 70 | end, 71 | R1, 72 | replicas() 73 | ), 74 | 75 | {StateBeforeMerge, _} = R2, 76 | 77 | R3 = case WithSync of 78 | true -> 79 | %% each replica synchronizes with its neighbors 80 | lists:foldl( 81 | fun(Replica, In) -> 82 | lists:foldl( 83 | fun(Neighbor, {StateIn, MetricsIn}) -> 84 | 85 | %% get the state that would be received in a message 86 | RemoteState = orddict:fetch(Neighbor, StateBeforeMerge), 87 | %& get the current local state 88 | LocalState = orddict:fetch(Replica, StateIn), 89 | 90 | {TimeDelta, Delta} = timer:tc( 91 | fun() -> 92 | %% which part of the remote state inflates the local state 93 | Type:delta(RemoteState, {state, LocalState}) 94 | end 95 | ), 96 | 97 | {TimeBottom, false} = timer:tc( 98 | fun() -> 99 | %% check if the delta is bottom 100 | Type:is_bottom(Delta) 101 | end 102 | ), 103 | 104 | {TimeMerge, Merged} = timer:tc( 105 | fun() -> 106 | %% merge the received state 107 | Type:merge(LocalState, RemoteState) 108 | end 109 | ), 110 | 111 | {TimeInflation, true} = timer:tc( 112 | fun() -> 113 | %% check if there was a strict inflation 114 | Type:is_strict_inflation(LocalState, Merged) 115 | end 116 | ), 117 | 118 | StateOut = orddict:store(Replica, Merged, StateIn), 119 | MetricsOut0 = orddict:append(delta, 120 | TimeDelta, 121 | MetricsIn), 122 | MetricsOut1 = orddict:append(is_bottom, 123 | TimeBottom, 124 | MetricsOut0), 125 | MetricsOut2 = orddict:append(is_strict_inflation, 126 | TimeInflation, 127 | MetricsOut1), 128 | MetricsOut3 = orddict:append(merge, 129 | TimeMerge, 130 | MetricsOut2), 131 | {StateOut, MetricsOut3} 132 | end, 133 | In, 134 | neighbors(config(topology), Replica) 135 | ) 136 | end, 137 | R2, 138 | replicas() 139 | ); 140 | false -> 141 | R2 142 | end, 143 | 144 | %% perform query and join_decompositions 145 | lists:foldl( 146 | fun(Replica, {StateIn, MetricsIn}) -> 147 | Value = orddict:fetch(Replica, StateIn), 148 | 149 | {TimeQuery, _} = timer:tc( 150 | fun() -> 151 | Type:query(Value) 152 | end 153 | ), 154 | 155 | {TimeJD, _} = timer:tc( 156 | fun() -> 157 | Type:join_decomposition(Value) 158 | end 159 | ), 160 | 161 | MetricsOut0 = orddict:append(query, 162 | TimeQuery, 163 | MetricsIn), 164 | MetricsOut1 = orddict:append(join_decompositions, 165 | TimeJD, 166 | MetricsOut0), 167 | {StateIn, MetricsOut1} 168 | end, 169 | R3, 170 | replicas() 171 | ) 172 | end, 173 | R0, 174 | Seq 175 | ). 176 | 177 | %% @private 178 | replicas() -> 179 | lists:seq(1, config(replica_number)). 180 | 181 | %% @private Get the neighbors of a replica in a ring topology. 182 | neighbors(ring, Replica) -> 183 | ReplicaNumber = config(replica_number), 184 | 185 | Left = case Replica of 186 | 1 -> 187 | ReplicaNumber; 188 | _ -> 189 | Replica - 1 190 | end, 191 | Right = case Replica of 192 | ReplicaNumber -> 193 | 1; 194 | _ -> 195 | Replica + 1 196 | end, 197 | [Left, Right]. 198 | 199 | %% @private 200 | part(Number) -> 201 | AddNumber = config(add_number), 202 | D2 = AddNumber div 2, 203 | D4 = AddNumber div 4, 204 | case Number of 205 | 1 -> 206 | lists:seq(1, D4); %% from 0% to 25% 207 | 2 -> 208 | lists:seq(D4 + 1, D2 + D4); %% from 25% to 75% 209 | 3 -> 210 | lists:seq(D2 + D4 + 1, AddNumber) %% from 75% to 100% 211 | end. 212 | 213 | %% @private 214 | create_element(Replica, I) -> 215 | integer_to_list(Replica) ++ "#####" ++ integer_to_list(I). 216 | 217 | 218 | %% @private config 219 | config(topology) -> ring; 220 | config(replica_number) -> 6; 221 | config(add_number) -> 100. 222 | -------------------------------------------------------------------------------- /include/pure_type.hrl: -------------------------------------------------------------------------------- 1 | -define(AA, keep_op1_add_op2). 2 | -define(AR, keep_op1_dont_add_op2). 3 | -define(RA, remove_op1_add_op2). 4 | -------------------------------------------------------------------------------- /include/state_type.hrl: -------------------------------------------------------------------------------- 1 | -define(AWMAP_TYPE, state_awmap). 2 | -define(AWSET_TYPE, state_awset). 3 | -define(BCOUNTER_TYPE, state_bcounter). 4 | -define(BOOLEAN_TYPE, state_boolean). 5 | -define(DWFLAG_TYPE, state_dwflag). 6 | -define(EWFLAG_TYPE, state_ewflag). 7 | -define(GCOUNTER_TYPE, state_gcounter). 8 | -define(GMAP_TYPE, state_gmap). 9 | -define(GSET_TYPE, state_gset). 10 | -define(IVAR_TYPE, state_ivar). 11 | -define(LEXCOUNTER_TYPE, state_lexcounter). 12 | -define(LWWREGISTER_TYPE, state_lwwregister). 13 | -define(MAX_INT_TYPE, state_max_int). 14 | -define(MVREGISTER_TYPE, state_mvregister). 15 | -define(MVMAP_TYPE, state_mvmap). 16 | -define(ORSET_TYPE, state_orset). 17 | -define(PAIR_TYPE, state_pair). 18 | -define(PNCOUNTER_TYPE, state_pncounter). 19 | -define(TWOPSET_TYPE, state_twopset). 20 | -------------------------------------------------------------------------------- /priv/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lasp-lang/types/e35b9b3a32b8f580daff1a7d1102813ad530835e/priv/.gitkeep -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, []}. 2 | 3 | {dialyzer, [ 4 | {plt_extra_apps, [sasl, eunit, syntax_tools, compiler]}, 5 | {warnings, [unknown]} 6 | ]}. 7 | 8 | {xref_checks, [undefined_function_calls]}. 9 | {erl_opts, [debug_info, 10 | warnings_as_errors, 11 | {platform_define, "^[0-9]+", namespaced_types}]}. 12 | {cover_enabled, true}. 13 | {eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}. 14 | {edoc_opts, [{preprocess, true}]}. 15 | 16 | {plugins, [rebar3_proper]}. 17 | 18 | {profiles, [ 19 | {test, [ 20 | {deps, [{proper, "1.3.0"}]} 21 | ]}, 22 | {lint, [ 23 | {plugins, [{rebar3_lint, "0.1.10"}]} 24 | ]} 25 | ]}. 26 | 27 | {relx, [{release, {types, "0.0.1"}, 28 | [types]}, 29 | 30 | {extended_start_script, true}, 31 | {overlay, [{mkdir, "data"}]} 32 | ]}. 33 | 34 | {proper_opts, [{numtests, 1000}]}. 35 | 36 | {elvis, 37 | [#{dirs => ["src"], 38 | filter => "*.erl", 39 | rules => [ 40 | %% {elvis_style, line_length, 41 | %% #{ignore => [], 42 | %% limit => 80, 43 | %% skip_comments => false}}, 44 | {elvis_style, no_tabs}, 45 | {elvis_style, no_trailing_whitespace}, 46 | {elvis_style, macro_names, #{ignore => []}}, 47 | %% {elvis_style, macro_module_names}, 48 | {elvis_style, operator_spaces, #{rules => [{right, ","}, 49 | {right, "++"}, 50 | {left, "++"}]}}, 51 | %% {elvis_style, nesting_level, #{level => 3}}, 52 | {elvis_style, god_modules, 53 | #{limit => 25, 54 | ignore => []}}, 55 | {elvis_style, no_if_expression}, 56 | %% {elvis_style, invalid_dynamic_call, #{ignore => []}}, 57 | {elvis_style, used_ignored_variable}, 58 | {elvis_style, no_behavior_info}, 59 | { 60 | elvis_style, 61 | module_naming_convention, 62 | #{regex => "^[a-z]([a-z0-9]*_?)*(_SUITE)?$", 63 | ignore => []} 64 | }, 65 | { 66 | elvis_style, 67 | function_naming_convention, 68 | #{regex => "^([a-z][a-z0-9]*_?)*$"} 69 | }, 70 | {elvis_style, state_record_and_type}, 71 | {elvis_style, no_spec_with_records} 72 | %% {elvis_style, dont_repeat_yourself, #{min_complexity => 10}} 73 | %% {elvis_style, no_debug_call, #{ignore => []}} 74 | ] 75 | }, 76 | #{dirs => ["."], 77 | filter => "Makefile", 78 | rules => [{elvis_project, no_deps_master_erlang_mk, #{ignore => []}}, 79 | {elvis_project, protocol_for_deps_erlang_mk, #{ignore => []}}] 80 | }, 81 | #{dirs => ["."], 82 | filter => "rebar.config", 83 | rules => [{elvis_project, no_deps_master_rebar, #{ignore => []}}, 84 | {elvis_project, protocol_for_deps_rebar, #{ignore => []}}] 85 | } 86 | ] 87 | }. 88 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/async_iterate.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(async_iterate). 21 | -author("Christopher S. Meiklejohn "). 22 | 23 | -behaviour(gen_server). 24 | 25 | %% API 26 | -export([start_link/2, 27 | iterator/0, 28 | next/1]). 29 | 30 | %% gen_server callbacks 31 | -export([init/1, 32 | handle_call/3, 33 | handle_cast/2, 34 | handle_info/2, 35 | terminate/2, 36 | code_change/3]). 37 | 38 | -record(state, {tid :: ets:tid(), 39 | function :: function(), 40 | next_position :: non_neg_integer(), 41 | finished :: boolean()}). 42 | 43 | -type state() :: #state{}. 44 | 45 | %% Sleep interval when waiting for more data. 46 | -define(INTERVAL, 300). 47 | 48 | %%%=================================================================== 49 | %%% API 50 | %%%=================================================================== 51 | 52 | %% @doc Same as start_link([]). 53 | -spec start_link(atom(), function()) -> {ok, pid()} | ignore | {error, term()}. 54 | start_link(Unique, Function) -> 55 | gen_server:start_link(?MODULE, [Unique, Function], []). 56 | 57 | %% @doc Return an iterator. 58 | iterator() -> 59 | gen_server:call(?MODULE, iterator, infinity). 60 | 61 | %% @doc Get the next value. 62 | next(Continuation) -> 63 | gen_server:call(?MODULE, {next, Continuation}, infinity). 64 | 65 | %%%=================================================================== 66 | %%% gen_server callbacks 67 | %%%=================================================================== 68 | 69 | %% @private 70 | -spec init([term()]) -> {ok, state()}. 71 | init([Unique, Function]) -> 72 | %% Generate an ets table to store results. 73 | Tid = ets:new(Unique, []), 74 | 75 | %% Trap exits. 76 | process_flag(trap_exit, true), 77 | 78 | %% Spawn function to populate ets table and pass in our process 79 | %% identifier. 80 | spawn_link(fun() -> 81 | Function(self()) 82 | end), 83 | 84 | {ok, #state{function=Function, 85 | tid=Tid, 86 | next_position=0, 87 | finished=false}}. 88 | 89 | %% @private 90 | -spec handle_call(term(), {pid(), term()}, state()) -> 91 | {reply, term(), state()}. 92 | handle_call(iterator, _From, #state{tid=Tid}=State) -> 93 | {[Match], Continuation} = ets:select(Tid, [{{'$1', '$2'}, [], ['$2']}], 1), 94 | {reply, {ok, {Match, Continuation}}, State}; 95 | handle_call({next, Continuation}, From, #state{finished=Finished}=State) -> 96 | read(From, Finished, Continuation), 97 | {noreply, State}; 98 | handle_call(_Msg, _From, State) -> 99 | {reply, ok, State}. 100 | 101 | %% @private 102 | -spec handle_cast(term(), state()) -> {noreply, state()}. 103 | handle_cast(_Msg, State) -> 104 | {noreply, State}. 105 | 106 | %% @private 107 | -spec handle_info(term(), state()) -> {noreply, state()}. 108 | handle_info({retry, From, Continuation}, #state{finished=Finished}=State) -> 109 | read(From, Finished, Continuation), 110 | {noreply, State}; 111 | handle_info({'EXIT', _From, normal}, State) -> 112 | %% Population function quit normally; ignore as it most likely means 113 | %% that the results are fully populated. 114 | {noreply, State#state{finished=true}}; 115 | handle_info({'EXIT', _From, Reason}, State) -> 116 | %% Abnormal exit from population function; quit. 117 | {stop, {error, {function_terminated, Reason}}, State}; 118 | handle_info({value, Value}, #state{tid=Tid, next_position=NextPosition}=State) -> 119 | %% Received a value from the function; populate next position in the 120 | %% ets table. 121 | true = ets:insert(Tid, [{NextPosition, Value}]), 122 | {noreply, State#state{next_position=NextPosition+1}}; 123 | handle_info(_Msg, State) -> 124 | {noreply, State}. 125 | 126 | %% @private 127 | -spec terminate(term(), state()) -> term(). 128 | terminate(_Reason, _State) -> 129 | ok. 130 | 131 | %% @private 132 | -spec code_change(term() | {down, term()}, state(), term()) -> {ok, state()}. 133 | code_change(_OldVsn, State, _Extra) -> 134 | {ok, State}. 135 | 136 | %%%=================================================================== 137 | %%% Internal functions 138 | %%%=================================================================== 139 | 140 | %% @private 141 | read(From, Finished, Continuation) -> 142 | case ets:select(Continuation) of 143 | '$end_of_table' -> 144 | case Finished of 145 | true -> 146 | %% We've reached the end of the results and know 147 | %% that population is complete, reply immediately 148 | %% with ok. 149 | gen_server:reply(From, ok); 150 | false -> 151 | %% We aren't done yet, therefore, schedule response 152 | %% for the future. 153 | erlang:send_after(?INTERVAL, self(), {retry, From, Continuation}) 154 | end; 155 | {[Match], Continuation} -> 156 | %% Got result; reply immediately. 157 | gen_server:reply(From, {ok, {Match, Continuation}}) 158 | end. 159 | -------------------------------------------------------------------------------- /src/causal_context.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Causal Context. 21 | %% 22 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 23 | %% Delta State Replicated Data Types (2016) 24 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 25 | 26 | -module(causal_context). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -export([ 30 | new/0, 31 | from_dot_set/1, 32 | dots/1, 33 | add_dot/2, 34 | next_dot/2, 35 | is_empty/1, 36 | is_element/2, 37 | union/2, 38 | to_dot/1 39 | ]). 40 | 41 | -export_type([causal_context/0]). 42 | 43 | -type dot_set() :: dot_store:dot_set(). 44 | -type compressed() :: orddict:orddict(dot_store:dot_actor(), 45 | dot_store:dot_sequence()). 46 | -type causal_context() :: {compressed(), dot_set()}. 47 | 48 | %% @doc Create an empty Causal Context. 49 | -spec new() -> causal_context(). 50 | new() -> 51 | {orddict:new(), dot_set:new()}. 52 | 53 | %% @doc Create a CausalContext from a DotSet. 54 | -spec from_dot_set(dot_set()) -> causal_context(). 55 | from_dot_set(DotSet) -> 56 | dot_set:fold( 57 | fun(Dot, CausalContext) -> 58 | causal_context:add_dot(Dot, CausalContext) 59 | end, 60 | causal_context:new(), 61 | DotSet 62 | ). 63 | 64 | %% @doc Return a list of dots from a CausalContext. 65 | -spec dots(causal_context()) -> dot_set(). 66 | dots({Compressed, DotSet}) -> 67 | CompressedDots = orddict:fold( 68 | fun(Actor, Sequence, Acc0) -> 69 | lists:foldl( 70 | fun(I, Acc1) -> 71 | dot_set:add_dot({Actor, I}, Acc1) 72 | end, 73 | Acc0, 74 | lists:seq(1, Sequence) 75 | ) 76 | end, 77 | dot_set:new(), 78 | Compressed 79 | ), 80 | 81 | dot_set:union(CompressedDots, DotSet). 82 | 83 | %% @doc Add a dot to the CausalContext. 84 | -spec add_dot(dot_store:dot(), causal_context()) -> causal_context(). 85 | add_dot({Actor, Sequence}=Dot, {Compressed0, DotSet0}=CC) -> 86 | Current = orddict_ext:fetch(Actor, Compressed0, 0), 87 | 88 | case Sequence == Current + 1 of 89 | true -> 90 | %% update the compressed component 91 | Compressed1 = orddict:store(Actor, Sequence, Compressed0), 92 | 93 | %% and try to compress if the next 94 | %% dot of this actor belongs to the 95 | %% set of outstanding dots 96 | CC1 = {Compressed1, DotSet0}, 97 | NextDot = {Actor, Sequence + 1}, 98 | case dot_set:is_element(NextDot, DotSet0) of 99 | true -> compress(CC1); 100 | false -> CC1 101 | end; 102 | false -> 103 | case Sequence > Current + 1 of 104 | true -> 105 | %% store in the DotSet if in the future 106 | DotSet1 = dot_set:add_dot(Dot, DotSet0), 107 | {Compressed0, DotSet1}; 108 | false -> 109 | %% dot already in the CausalContext. 110 | CC 111 | end 112 | end. 113 | 114 | %% @doc Get `dot_actor()''s next dot 115 | -spec next_dot(dot_store:dot_actor(), causal_context()) -> 116 | dot_store:dot(). 117 | next_dot(Actor, {Compressed, _DotSet}) -> 118 | MaxValue = orddict_ext:fetch(Actor, Compressed, 0), 119 | {Actor, MaxValue + 1}. 120 | 121 | %% @doc Check if a Causal Context is empty. 122 | -spec is_empty(causal_context()) -> boolean(). 123 | is_empty({Compressed, DotSet}) -> 124 | orddict:is_empty(Compressed) andalso dot_set:is_empty(DotSet). 125 | 126 | %% @doc Check if a dot belongs to the CausalContext. 127 | -spec is_element(dot_store:dot(), causal_context()) -> boolean(). 128 | is_element({Actor, Sequence}=Dot, {Compressed, DotSet}) -> 129 | Current = orddict_ext:fetch(Actor, Compressed, 0), 130 | Sequence =< Current orelse dot_set:is_element(Dot, DotSet). 131 | 132 | %% @doc Merge two Causal Contexts. 133 | -spec union(causal_context(), causal_context()) -> causal_context(). 134 | union({CompressedA, DotSetA}, {CompressedB, DotSetB}) -> 135 | Compressed = orddict:merge( 136 | fun(_Actor, SequenceA, SequenceB) -> 137 | max(SequenceA, SequenceB) 138 | end, 139 | CompressedA, 140 | CompressedB 141 | ), 142 | DotSet = dot_set:union(DotSetA, DotSetB), 143 | compress({Compressed, DotSet}). 144 | 145 | %% @private Try to add the dots in the DotSet to the compressed 146 | %% component. 147 | -spec compress(causal_context()) -> causal_context(). 148 | compress({Compressed, DotSet}) -> 149 | dot_set:fold( 150 | fun(Dot, CausalContext) -> 151 | add_dot(Dot, CausalContext) 152 | end, 153 | {Compressed, dot_set:new()}, 154 | DotSet 155 | ). 156 | 157 | %% @private Convert a CausalContext with a single dot, to that dot. 158 | -spec to_dot(causal_context()) -> dot_store:dot(). 159 | to_dot({[Dot], []}) -> Dot; 160 | to_dot({[], [Dot]}) -> Dot. 161 | -------------------------------------------------------------------------------- /src/dot_fun.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc DotFun. 21 | %% 22 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 23 | %% Delta State Replicated Data Types (2016) 24 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 25 | 26 | -module(dot_fun). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -include("state_type.hrl"). 30 | 31 | -behaviour(dot_store). 32 | 33 | -export([ 34 | new/0, 35 | is_empty/1, 36 | is_element/2, 37 | fetch/2, 38 | store/3, 39 | to_list/1, 40 | filter/2 41 | ]). 42 | 43 | -type dot_fun() :: dot_store:dot_fun(). 44 | 45 | %% @doc Create an empty DotFun. 46 | -spec new() -> dot_fun(). 47 | new() -> 48 | orddict:new(). 49 | 50 | %% @doc Check if a DotFun is empty. 51 | -spec is_empty(dot_fun()) -> boolean(). 52 | is_empty(DotFun) -> 53 | orddict:is_empty(DotFun). 54 | 55 | %% @doc Check if a dot belongs to the DotFun. 56 | -spec is_element(dot_store:dot(), dot_fun()) -> boolean(). 57 | is_element(Dot, DotFun) -> 58 | orddict:is_key(Dot, DotFun). 59 | 60 | %% @doc Given a Dot and a DotFun, get the correspondent CRDT value. 61 | -spec fetch(dot_store:dot(), dot_fun()) -> state_type:crdt(). 62 | fetch(Dot, DotFun) -> 63 | orddict:fetch(Dot, DotFun). 64 | 65 | %% @doc Stores a new CRDT in the DotFun with Dot as key. 66 | -spec store(dot_store:dot(), state_type:crdt(), dot_fun()) -> 67 | dot_fun(). 68 | store(Dot, CRDT, DotFun) -> 69 | orddict:store(Dot, CRDT, DotFun). 70 | 71 | %% @doc Converts the DotFun to a list of pairs {Dot, CRDT}. 72 | -spec to_list(dot_fun()) -> list({dot_store:dot(), state_type:crdt()}). 73 | to_list(DotFun) -> 74 | orddict:to_list(DotFun). 75 | 76 | %% @doc Filter DotFun. 77 | -spec filter(function(), dot_fun()) -> dot_fun(). 78 | filter(F, DotFun) -> 79 | orddict:filter(F, DotFun). 80 | -------------------------------------------------------------------------------- /src/dot_map.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc DotMap. 21 | %% 22 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 23 | %% Delta State Replicated Data Types (2016) 24 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 25 | 26 | -module(dot_map). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -behaviour(dot_store). 30 | 31 | -export([ 32 | new/0, 33 | is_empty/1, 34 | fetch_keys/1, 35 | fetch/3, 36 | store/3, 37 | merge/4, 38 | any/2, 39 | fold/3 40 | ]). 41 | 42 | -type dot_map() :: dot_store:dot_map(). 43 | 44 | %% @doc Create an empty DotMap. 45 | -spec new() -> dot_map(). 46 | new() -> 47 | orddict:new(). 48 | 49 | %% @doc Check if a DotMap is empty. 50 | -spec is_empty(dot_map()) -> boolean(). 51 | is_empty(DotMap) -> 52 | orddict:is_empty(DotMap). 53 | 54 | %% @doc Given a key, a DotMap and a default, 55 | %% return: 56 | %% - the correspondent value, if key present in the DotMap 57 | %% - default, otherwise 58 | -spec fetch(term(), dot_map(), dot_store:dot_store() | undefined) -> 59 | dot_store:dot_store(). 60 | fetch(Key, DotMap, Default) -> 61 | orddict_ext:fetch(Key, DotMap, Default). 62 | 63 | %% @doc Get a list of keys in the DotMap. 64 | -spec fetch_keys(dot_store:dot_map()) -> [term()]. 65 | fetch_keys(DotMap) -> 66 | orddict:fetch_keys(DotMap). 67 | 68 | %% @doc Stores a new {Key, DotStore} pair in the DotMap. 69 | %% If `Key` already in the DotMap, then its value is replaced. 70 | -spec store(term(), dot_store:dot_store(), dot_map()) -> dot_map(). 71 | store(Key, DotStore, DotMap) -> 72 | orddict:store(Key, DotStore, DotMap). 73 | 74 | %% @doc Merge two DotMap. 75 | -spec merge(function(), dot_store:dot_store(), 76 | dot_map(), dot_map()) -> dot_map(). 77 | merge(_Fun, _Default, [], []) -> 78 | []; 79 | merge(Fun, Default, [{Key, ValueA} | RestA], []) -> 80 | do_merge(Fun, Default, Key, ValueA, Default, RestA, []); 81 | merge(Fun, Default, [], [{Key, ValueB} | RestB]) -> 82 | do_merge(Fun, Default, Key, Default, ValueB, [], RestB); 83 | merge(Fun, Default, [{Key, ValueA} | RestA], 84 | [{Key, ValueB} | RestB]) -> 85 | do_merge(Fun, Default, Key, ValueA, ValueB, RestA, RestB); 86 | merge(Fun, Default, [{KeyA, ValueA} | RestA], 87 | [{KeyB, _} | _]=RestB) when KeyA < KeyB -> 88 | do_merge(Fun, Default, KeyA, ValueA, Default, RestA, RestB); 89 | merge(Fun, Default, [{KeyA, _} | _]=RestA, 90 | [{KeyB, ValueB} | RestB]) when KeyA > KeyB -> 91 | do_merge(Fun, Default, KeyB, Default, ValueB, RestA, RestB). 92 | 93 | do_merge(Fun, Default, Key, ValueA, ValueB, RestA, RestB) -> 94 | case Fun(ValueA, ValueB) of 95 | Default -> 96 | merge(Fun, Default, RestA, RestB); 97 | Value -> 98 | [{Key, Value} | merge(Fun, Default, RestA, RestB)] 99 | end. 100 | 101 | %% @doc True if Pred is true for at least one entry in the DotMap. 102 | -spec any(function(), dot_map()) -> boolean(). 103 | any(Pred, DotMap) -> 104 | lists:any(Pred, DotMap). 105 | 106 | %% @doc Fold a DotMap. 107 | -spec fold(function(), term(), dot_map()) -> term(). 108 | fold(Fun, AccIn, DotMap) -> 109 | orddict:fold(Fun, AccIn, DotMap). 110 | -------------------------------------------------------------------------------- /src/dot_set.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc DotSet. 21 | %% 22 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 23 | %% Delta State Replicated Data Types (2016) 24 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 25 | 26 | -module(dot_set). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -behaviour(dot_store). 30 | 31 | -export([ 32 | new/0, 33 | from_dots/1, 34 | add_dot/2, 35 | is_empty/1, 36 | is_element/2, 37 | union/2, 38 | intersection/2, 39 | subtract/2, 40 | subtract_causal_context/2, 41 | fold/3 42 | ]). 43 | 44 | -type dot_set() :: dot_store:dot_set(). 45 | 46 | %% @doc Create an empty DotSet. 47 | -spec new() -> dot_set(). 48 | new() -> 49 | ordsets:new(). 50 | 51 | %% @doc Create a DotSet from a list of dots. 52 | -spec from_dots(list(dot_store:dot())) -> dot_set(). 53 | from_dots(Dots) -> 54 | lists:foldl( 55 | fun(Dot, DotSet) -> 56 | dot_set:add_dot(Dot, DotSet) 57 | end, 58 | dot_set:new(), 59 | Dots 60 | ). 61 | 62 | %% @doc Add a dot to the DotSet. 63 | -spec add_dot(dot_store:dot(), dot_set()) -> dot_set(). 64 | add_dot(Dot, DotSet) -> 65 | ordsets:add_element(Dot, DotSet). 66 | 67 | %% @doc Check if a DotSet is empty. 68 | -spec is_empty(dot_set()) -> boolean(). 69 | is_empty(DotSet) -> 70 | ordsets:size(DotSet) == 0. 71 | 72 | %% @doc Check if a dot belongs to the DotSet. 73 | -spec is_element(dot_store:dot(), dot_set()) -> boolean(). 74 | is_element(Dot, DotSet) -> 75 | ordsets:is_element(Dot, DotSet). 76 | 77 | %% @doc Union two DotSets. 78 | -spec union(dot_set(), dot_set()) -> dot_set(). 79 | union(DotSetA, DotSetB) -> 80 | ordsets:union(DotSetA, DotSetB). 81 | 82 | %% @doc Intersect two DotSets. 83 | -spec intersection(dot_set(), dot_set()) -> dot_set(). 84 | intersection(DotSetA, DotSetB) -> 85 | ordsets:intersection(DotSetA, DotSetB). 86 | 87 | %% @doc Subtract a DotSet from a DotSet. 88 | -spec subtract(dot_set(), dot_set()) -> dot_set(). 89 | subtract(DotSetA, DotSetB) -> 90 | ordsets:subtract(DotSetA, DotSetB). 91 | 92 | %% @doc Subtract a CausalContext from a DotSet. 93 | -spec subtract_causal_context(dot_set(), 94 | causal_context:causal_context()) -> 95 | dot_set(). 96 | subtract_causal_context(DotSet, CausalContext) -> 97 | ordsets:filter( 98 | fun(Dot) -> 99 | not causal_context:is_element(Dot, CausalContext) 100 | end, 101 | DotSet 102 | ). 103 | 104 | %% @doc Fold a DotSet. 105 | -spec fold(function(), term(), dot_set()) -> term(). 106 | fold(Fun, AccIn, DotSet) -> 107 | ordsets:fold(Fun, AccIn, DotSet). 108 | -------------------------------------------------------------------------------- /src/dot_store.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc DotStores: 21 | %% - DotSet 22 | %% - DotFun 23 | %% - DotMap 24 | %% 25 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 26 | %% Delta State Replicated Data Types (2016) 27 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 28 | 29 | -module(dot_store). 30 | -author("Vitor Enes Duarte "). 31 | 32 | -export_type([dot_actor/0, 33 | dot_sequence/0, 34 | dot/0, 35 | dot_set/0, 36 | dot_fun/0, 37 | dot_map/0, 38 | dot_store/0, 39 | type/0]). 40 | 41 | -type dot_actor() :: term(). 42 | -type dot_sequence() :: pos_integer(). 43 | -type dot() :: {dot_actor(), dot_sequence()}. 44 | 45 | -type dot_set() :: ordsets:ordset(dot()). 46 | -type dot_fun() :: orddict:orddict(dot(), term()). 47 | -type dot_map() :: orddict:orddict(term(), dot_store()). 48 | 49 | -type type() :: dot_set | 50 | {dot_fun, state_type:state_type()} | 51 | {dot_map, type()}. 52 | -type dot_store() :: dot_set() | dot_fun() | dot_map(). 53 | 54 | 55 | %% @doc Create an empty DotStore. 56 | -callback new() -> dot_store(). 57 | 58 | %% @doc Check if a DotStore is empty. 59 | -callback is_empty(dot_store()) -> boolean(). 60 | -------------------------------------------------------------------------------- /src/lists_ext.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(lists_ext). 21 | -author("Vitor Enes Duarte "). 22 | 23 | -export([iterate_until/2]). 24 | 25 | -spec iterate_until(function(), list(any())) -> 26 | boolean(). 27 | iterate_until(_Fun, []) -> 28 | true; 29 | iterate_until(Fun, [H | T]) -> 30 | Fun(H) andalso iterate_until(Fun, T). 31 | -------------------------------------------------------------------------------- /src/orddict_ext.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(orddict_ext). 21 | -author("Vitor Enes Duarte "). 22 | 23 | -export([equal/3, 24 | fetch/3]). 25 | 26 | -spec equal(orddict:orddict(), orddict:orddict(), function()) -> 27 | boolean(). 28 | equal(Dict1, Dict2, Fun) -> 29 | orddict:size(Dict1) == orddict:size(Dict2) andalso 30 | lists_ext:iterate_until( 31 | fun({Key, Value1}) -> 32 | case orddict:find(Key, Dict2) of 33 | {ok, Value2} -> 34 | Fun(Value1, Value2); 35 | error -> 36 | false 37 | end 38 | end, 39 | Dict1 40 | ). 41 | 42 | %% @doc 43 | fetch(K, M, Default) -> 44 | case orddict:find(K, M) of 45 | {ok, V} -> 46 | V; 47 | error -> 48 | Default 49 | end. 50 | -------------------------------------------------------------------------------- /src/ordsets_ext.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(ordsets_ext). 21 | -author("Vitor Enes Duarte "). 22 | 23 | -export([equal/2]). 24 | 25 | -spec equal(ordsets:ordset(any()), ordsets:ordset(any())) -> 26 | boolean(). 27 | %% @doc Two sets s1 and s2 are equal if: 28 | %% - s1 is subset of s2 29 | %% - s2 is subset of s1 30 | equal(Set1, Set2) -> 31 | ordsets:is_subset(Set1, Set2) andalso 32 | ordsets:is_subset(Set2, Set1). 33 | -------------------------------------------------------------------------------- /src/pure_awset.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure AWSet CRDT: pure op-based add-wins observed-remove set 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_awset). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | -behaviour(pure_polog). 32 | 33 | -define(TYPE, ?MODULE). 34 | 35 | -include("pure_type.hrl"). 36 | 37 | -ifdef(TEST). 38 | -include_lib("eunit/include/eunit.hrl"). 39 | -endif. 40 | 41 | -export([new/0, new/1]). 42 | -export([mutate/3, query/1, equal/2, reset/2]). 43 | -export([redundant/2, remove_redundant_crystal/2, remove_redundant_polog/2, check_stability/2]). 44 | 45 | -export_type([pure_awset/0, pure_awset_op/0]). 46 | 47 | -opaque pure_awset() :: {?TYPE, payload()}. 48 | -type payload() :: {pure_type:polog(), ordsets:ordset(any())}. 49 | -type pure_awset_op() :: {add, pure_type:element()} | {rmv, pure_type:element()}. 50 | 51 | %% @doc Create a new, empty `pure_awset()' 52 | -spec new() -> pure_awset(). 53 | new() -> 54 | {?TYPE, {orddict:new(), ordsets:new()}}. 55 | 56 | %% @doc Create a new, empty `pure_awset()' 57 | -spec new([term()]) -> pure_awset(). 58 | new([]) -> 59 | new(). 60 | 61 | %% @doc Check redundancy `pure_awset()' 62 | %% For a given element, either an "add" or a "rmv" in the future of an "add" make it redundant. 63 | %% Called in remove_redundant(). 64 | -spec redundant({pure_type:id(), pure_awset_op()}, {pure_type:id(), pure_awset_op()}) -> 65 | atom(). 66 | redundant({VV1, {add, Elem1}}, {VV2, {_X, Elem2}}) -> 67 | case Elem1 =:= Elem2 andalso pure_trcb:happened_before(VV1, VV2) of 68 | true -> 69 | ?RA; 70 | false -> 71 | ?AA 72 | end. 73 | 74 | %% @doc Removes redundant operations from POLog of `pure_awset()' 75 | %% Called upon updating (add, rmv) the `pure_awset()' 76 | -spec remove_redundant_polog({pure_type:id(), pure_awset_op()}, pure_awset()) -> {boolean(), pure_awset()}. 77 | remove_redundant_polog({VV1, Op}, {?TYPE, {POLog0, ORSet}}) -> 78 | POLog1 = orddict:fold( 79 | fun(Key, Value, Acc) -> 80 | case redundant({Key, Value}, {VV1, Op}) of 81 | ?AA -> 82 | orddict:store(Key, Value, Acc); 83 | ?RA -> 84 | Acc 85 | end 86 | end, 87 | orddict:new(), 88 | POLog0 89 | ), 90 | {true, {?TYPE, {POLog1, ORSet}}}. 91 | 92 | %% @doc Removes redundant operations from Crystal of `pure_awset()' 93 | %% If there is an "add" or "rmv" for some element, that element does not need to be in the sequential set. 94 | %% Called upon updating (add, rmv) the `pure_awset()' 95 | -spec remove_redundant_crystal({pure_type:id(), pure_awset_op()}, pure_awset()) -> {boolean(), pure_awset()}. 96 | remove_redundant_crystal({_VV1, {_X, Elem}}, {?TYPE, {POLog, AWORSet}}) -> 97 | case ordsets:is_element(Elem, AWORSet) of 98 | true -> 99 | {true, {?TYPE, {POLog, ordsets:del_element(Elem, AWORSet)}}}; 100 | false -> 101 | {true, {?TYPE, {POLog, AWORSet}}} 102 | end. 103 | 104 | %% @doc Checks stable operations and remove them from POLog of `pure_awset()' 105 | -spec check_stability(pure_type:id(), pure_awset()) -> pure_awset(). 106 | check_stability(StableVV, {?TYPE, {POLog0, AWORSet0}}) -> 107 | {POLog1, AWORSet1} = orddict:fold( 108 | fun(Key, {_Op, Elem}=Value, {AccPOLog, AccORSet}) -> 109 | case pure_trcb:happened_before(Key, StableVV) of 110 | true -> 111 | {AccPOLog, ordsets:add_element(Elem, AccORSet)}; 112 | false -> 113 | {orddict:store(Key, Value, AccPOLog), AccORSet} 114 | end 115 | end, 116 | {orddict:new(), AWORSet0}, 117 | POLog0 118 | ), 119 | {?TYPE, {POLog1, AWORSet1}}. 120 | 121 | %% @doc Update a `pure_awset()'. 122 | -spec mutate(pure_awset_op(), pure_type:id(), pure_awset()) -> 123 | {ok, pure_awset()}. 124 | mutate({add, Elem}, VV, {?TYPE, {POLog, AWSet}}) -> 125 | {_, {?TYPE, {POLog0, AWSet0}}} = pure_polog:remove_redundant({VV, {add, Elem}}, {?TYPE, {POLog, AWSet}}), 126 | {ok, {?TYPE, {orddict:store(VV, {add, Elem}, POLog0), AWSet0}}}; 127 | mutate({rmv, Elem}, VV, {?TYPE, {POLog, AWSet}}) -> 128 | {_, {?TYPE, {POLog0, AWSet0}}} = pure_polog:remove_redundant({VV, {rmv, Elem}}, {?TYPE, {POLog, AWSet}}), 129 | {ok, {?TYPE, {POLog0, AWSet0}}}. 130 | 131 | %% @doc Clear/reset the state to initial state. 132 | -spec reset(pure_type:id(), pure_awset()) -> pure_awset(). 133 | reset(VV, {?TYPE, _}=CRDT) -> 134 | pure_type:reset(VV, CRDT). 135 | 136 | %% @doc Returns the value of the `pure_awset()'. 137 | %% This value is a set with all the elements in the `pure_awset()'. 138 | -spec query(pure_awset()) -> sets:set(pure_type:element()). 139 | query({?TYPE, {POLog0, AWSet0}}) -> 140 | Elements0 = ordsets:to_list(AWSet0), 141 | Elements1 = [El || {_Key, {_Op, El}} <- orddict:to_list(POLog0)], 142 | sets:from_list(lists:append(Elements0, Elements1)). 143 | 144 | 145 | %% @doc Equality for `pure_awset()'. 146 | -spec equal(pure_awset(), pure_awset()) -> boolean(). 147 | equal({?TYPE, {POLog1, AWSet1}}, {?TYPE, {POLog2, AWSet2}}) -> 148 | Fun = fun(Value1, Value2) -> Value1 == Value2 end, 149 | ordsets_ext:equal(AWSet1, AWSet2) andalso orddict_ext:equal(POLog1, POLog2, Fun). 150 | 151 | 152 | %% =================================================================== 153 | %% EUnit tests 154 | %% =================================================================== 155 | -ifdef(TEST). 156 | 157 | new_test() -> 158 | ?assertEqual({?TYPE, {orddict:new(), ordsets:new()}}, new()). 159 | 160 | redundant_test() -> 161 | ?assertEqual(?AA, redundant({[{0, 0}, {1, 0}], {add, <<"a">>}}, {[{0, 1}, {1, 1}], {add, <<"b">>}})), 162 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], {add, <<"a">>}}, {[{0, 1}, {1, 1}], {add, <<"a">>}})), 163 | ?assertEqual(?AA, redundant({[{0, 0}, {1, 0}], {add, <<"a">>}}, {[{0, 0}, {1, 0}], {add, <<"a">>}})). 164 | 165 | remove_redundant_crystal_test() -> 166 | {Redundant0, {?TYPE, {_POLog0, AWORSet0}}} = remove_redundant_crystal({[{0, 1}, {1, 2}, {2, 3}], {add, <<"a">>}}, {?TYPE, {[{0, 1}], [<<"a">>, <<"b">>, <<"c">>]}}), 167 | ?assertEqual(true, Redundant0), 168 | ?assertEqual([<<"b">>, <<"c">>], AWORSet0), 169 | {Redundant1, {?TYPE, {_POLog1, AWORSet1}}} = remove_redundant_crystal({[{0, 1}, {1, 2}, {2, 3}], {rmv, <<"a">>}}, {?TYPE, {[{0, 1}], [<<"a">>, <<"b">>, <<"c">>]}}), 170 | ?assertEqual(true, Redundant1), 171 | ?assertEqual([<<"b">>, <<"c">>], AWORSet1), 172 | {Redundant2, {?TYPE, {_POLog2, AWORSet2}}} = remove_redundant_crystal({[{0, 1}], {rmv, <<"d">>}}, {?TYPE, {[{0, 1}], [<<"a">>]}}), 173 | ?assertEqual(true, Redundant2), 174 | ?assertEqual([<<"a">>], AWORSet2). 175 | 176 | query_test() -> 177 | Set0 = new(), 178 | Set1 = {?TYPE, {[], [<<"a">>]}}, 179 | Set2 = {?TYPE, {[], [<<"a">>, <<"c">>]}}, 180 | Set3 = {?TYPE, {[{[{1, 2}], {add, <<"b">>}}], [<<"a">>]}}, 181 | Set4 = {?TYPE, {[{[{1, 3}], {add, <<"a">>}}], [<<"a">>]}}, 182 | ?assertEqual(sets:new(), query(Set0)), 183 | ?assertEqual(sets:from_list([<<"a">>]), query(Set1)), 184 | ?assertEqual(sets:from_list([<<"a">>, <<"c">>]), query(Set2)), 185 | ?assertEqual(sets:from_list([<<"a">>, <<"b">>]), query(Set3)), 186 | ?assertEqual(sets:from_list([<<"a">>]), query(Set4)). 187 | 188 | add_test() -> 189 | Set0 = new(), 190 | {ok, Set1} = mutate({add, <<"a">>}, [{0, 1}], Set0), 191 | {ok, Set2} = mutate({add, <<"b">>}, [{0, 2}], Set1), 192 | {ok, Set3} = mutate({add, <<"b">>}, [{0, 2}], Set2), 193 | {ok, Set4} = mutate({add, <<"b">>}, [{0, 3}], Set3), 194 | Set5 = {?TYPE, {[], [<<"a">>, <<"b">>, <<"c">>]}}, 195 | {ok, Set6} = mutate({add, <<"b">>}, [{0, 4}], Set5), 196 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}], []}}, Set1), 197 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}, {[{0, 2}], {add, <<"b">>}}], []}}, Set2), 198 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}, {[{0, 2}], {add, <<"b">>}}], []}}, Set3), 199 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}, {[{0, 3}], {add, <<"b">>}}], []}}, Set4), 200 | ?assertEqual({?TYPE, {[{[{0, 4}], {add, <<"b">>}}], [<<"a">>, <<"c">>]}}, Set6). 201 | 202 | rmv_test() -> 203 | Set1 = {?TYPE, {[{[{0, 1}], {add, <<"a">>}}, {[{0, 2}], {add, <<"b">>}}], []}}, 204 | {ok, Set2} = mutate({rmv, <<"b">>}, [{0, 3}], Set1), 205 | {ok, Set3} = mutate({rmv, <<"a">>}, [{0, 4}], Set2), 206 | {ok, Set4} = mutate({rmv, <<"a">>}, [{0, 0}], Set2), 207 | {ok, Set5} = mutate({rmv, <<"c">>}, [{0, 5}], Set2), 208 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}], []}}, Set2), 209 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}], []}}, Set4), 210 | ?assertEqual({?TYPE, {[{[{0, 1}], {add, <<"a">>}}], []}}, Set5), 211 | ?assertEqual({?TYPE, {[], []}}, Set3). 212 | 213 | reset_test() -> 214 | Set1 = {?TYPE, {[{[{0, 1}, {1, 2}], {add, <<"b">>}}, {[{0, 3}, {1, 4}], {add, <<"c">>}}, {[{0, 6}, {1, 5}], {add, <<"d">>}}], [<<"a">>]}}, 215 | Set2 = reset([{0, 5}, {1, 6}], Set1), 216 | ?assertEqual({?TYPE, {[{[{0, 6}, {1, 5}], {add, <<"d">>}}], []}}, Set2). 217 | 218 | check_stability_test() -> 219 | Set0 = new(), 220 | Set1 = check_stability([], Set0), 221 | ?assertEqual(Set0, Set1). 222 | 223 | equal_test() -> 224 | Set0 = {?TYPE, {[], [<<"a">>, <<"b">>, <<"c">>]}}, 225 | Set1 = {?TYPE, {[{k1, "c"}], [<<"a">>, <<"b">>]}}, 226 | Set2 = {?TYPE, {[{k1, "c"}], [<<"a">>, <<"b">>]}}, 227 | Set3 = {?TYPE, {[], [<<"a">>, <<"b">>, <<"c">>]}}, 228 | ?assert(equal(Set0, Set3)), 229 | ?assert(equal(Set1, Set2)), 230 | ?assertNot(equal(Set0, Set1)), 231 | ?assertNot(equal(Set2, Set3)). 232 | 233 | -endif. 234 | -------------------------------------------------------------------------------- /src/pure_dwflag.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure DWFlag CRDT: pure op-based disable-wins flag 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_dwflag). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | -behaviour(pure_polog). 32 | 33 | -define(TYPE, ?MODULE). 34 | 35 | -include("pure_type.hrl"). 36 | 37 | -ifdef(TEST). 38 | -include_lib("eunit/include/eunit.hrl"). 39 | -endif. 40 | 41 | -export([new/0, new/1]). 42 | -export([mutate/3, query/1, equal/2, reset/2]). 43 | -export([redundant/2, remove_redundant_crystal/2, remove_redundant_polog/2, check_stability/2]). 44 | 45 | -export_type([pure_dwflag/0, pure_dwflag_op/0]). 46 | 47 | -opaque pure_dwflag() :: {?TYPE, payload()}. 48 | -type payload() :: {pure_type:polog(), boolean()}. 49 | -type pure_dwflag_op() :: enable | disable. 50 | 51 | %% @doc Create a new, empty `pure_dwflag()' 52 | %% @doc Disable-wins flags start at True state 53 | -spec new() -> pure_dwflag(). 54 | new() -> 55 | {?TYPE, {orddict:new(), true}}. 56 | 57 | %% @doc Create a new, empty `pure_dwflag()' 58 | -spec new([term()]) -> pure_dwflag(). 59 | new([]) -> 60 | new(). 61 | 62 | %% @doc Check redundancy `pure_dwflag()' 63 | %% Called in remove_redundant(). 64 | -spec redundant({pure_type:id(), pure_dwflag_op()}, {pure_type:id(), pure_dwflag_op()}) -> 65 | atom(). 66 | redundant({VV1, Op1}, {VV2, Op2}) -> 67 | case pure_trcb:happened_before(VV1, VV2) of 68 | true -> 69 | ?RA; %% Op1 removed, Op2 added 70 | false -> %% VV1 and VV2 are concurrent 71 | case Op1 == Op2 of 72 | true -> 73 | ?AA; %% Op1 stays, Op2 added 74 | false -> 75 | case Op2 of 76 | disable -> 77 | ?RA; %% Op1 removed, Op2 added. Since disable wins 78 | enable -> 79 | ?AR %% Op1 stays, Op2 non added. Enable loses 80 | end 81 | end 82 | end. 83 | 84 | %% @doc Removes redundant operations from POLog of `pure_dwflag()' 85 | %% Called upon updating (enable, disable) the `pure_dwflag()' 86 | -spec remove_redundant_polog({pure_type:id(), pure_dwflag_op()}, pure_dwflag()) -> {boolean(), pure_dwflag()}. 87 | remove_redundant_polog({VV1, Op}, {?TYPE, {POLog0, Flag}}) -> 88 | case orddict:is_empty(POLog0) of 89 | true -> 90 | {true, {?TYPE, {POLog0, Flag}}}; 91 | false -> 92 | {POLog1, Add1} = orddict:fold( 93 | fun(Key, Value, {Acc, Add}) -> 94 | case redundant({Key, Value}, {VV1, Op}) of 95 | ?AA -> 96 | {orddict:store(Key, Value, Acc), Add}; 97 | ?RA -> 98 | {Acc, Add}; 99 | ?AR -> 100 | {orddict:store(Key, Value, Acc), Add andalso false} 101 | end 102 | end, 103 | {orddict:new(), true}, 104 | POLog0 105 | ), 106 | {Add1, {?TYPE, {POLog1, Flag}}} 107 | end. 108 | 109 | %% @doc Removes redundant operations from POLog of `pure_dwflag()' 110 | %% Called upon updating (enable, disable) the `pure_dwflag()' 111 | -spec remove_redundant_crystal({pure_type:id(), pure_dwflag_op()}, pure_dwflag()) -> {boolean(), pure_dwflag()}. 112 | remove_redundant_crystal({_VV1, _X}, {?TYPE, {POLog0, DWFlag}}) -> 113 | {true, {?TYPE, {POLog0, DWFlag}}}. 114 | 115 | %% @doc Checks stable operations and remove them from POLog of `pure_dwflag()' 116 | -spec check_stability(pure_type:id(), pure_dwflag()) -> pure_dwflag(). 117 | check_stability(_StableVV, {?TYPE, {POLog, DWFlag}}) -> 118 | {?TYPE, {POLog, DWFlag}}. 119 | 120 | %% @doc Update a `pure_dwflag()'. 121 | -spec mutate(pure_dwflag_op(), pure_type:id(), pure_dwflag()) -> 122 | {ok, pure_dwflag()}. 123 | mutate(Op, VV, {?TYPE, {POLog, PureDWFlag}}) -> 124 | {Add, {?TYPE, {POLog0, PureDWFlag0}}} = pure_polog:remove_redundant({VV, Op}, {?TYPE, {POLog, PureDWFlag}}), 125 | case Add of 126 | false -> 127 | {ok, {?TYPE, {POLog0, PureDWFlag0}}}; 128 | true -> 129 | {ok, {?TYPE, {orddict:store(VV, Op, POLog0), PureDWFlag0}}} 130 | end. 131 | 132 | %% @doc Clear/reset the state to initial state. 133 | -spec reset(pure_type:id(), pure_dwflag()) -> pure_dwflag(). 134 | reset(VV, {?TYPE, _}=CRDT) -> 135 | pure_type:reset(VV, CRDT). 136 | 137 | %% @doc Returns the value of the `pure_dwflag()'. 138 | %% This value is a a boolean value in the `pure_dwflag()'. 139 | -spec query(pure_dwflag()) -> boolean(). 140 | query({?TYPE, {POLog0, _PureDWFlag0}}) -> 141 | (orddict:is_empty(POLog0) orelse lists:member(enable, [Op || {_Key, Op} <- orddict:to_list(POLog0)])). 142 | 143 | 144 | 145 | %% @doc Equality for `pure_dwflag()'. 146 | -spec equal(pure_dwflag(), pure_dwflag()) -> boolean(). 147 | equal({?TYPE, {POLog1, PureDWFlag1}}, {?TYPE, {POLog2, PureDWFlag2}}) -> 148 | Fun = fun(Value1, Value2) -> Value1 == Value2 end, 149 | PureDWFlag1 == PureDWFlag2 andalso orddict_ext:equal(POLog1, POLog2, Fun). 150 | 151 | 152 | %% =================================================================== 153 | %% EUnit tests 154 | %% =================================================================== 155 | -ifdef(TEST). 156 | 157 | new_test() -> 158 | ?assertEqual({?TYPE, {orddict:new(), true}}, new()). 159 | 160 | redundant_test() -> 161 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], enable}, {[{0, 1}, {1, 1}], enable})), 162 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], enable}, {[{0, 1}, {1, 1}], disable})), 163 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], disable}, {[{0, 1}, {1, 1}], enable})), 164 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], disable}, {[{0, 1}, {1, 1}], disable})), 165 | ?assertEqual(?AA, redundant({[{0, 1}, {1, 0}], enable}, {[{0, 0}, {1, 1}], enable})), 166 | ?assertEqual(?AA, redundant({[{0, 1}, {1, 0}], disable}, {[{0, 0}, {1, 1}], disable})), 167 | ?assertEqual(?AR, redundant({[{0, 1}, {1, 0}], disable}, {[{0, 0}, {1, 1}], enable})), 168 | ?assertEqual(?RA, redundant({[{0, 1}, {1, 0}], enable}, {[{0, 0}, {1, 1}], disable})). 169 | 170 | query_test() -> 171 | Flag0 = new(), 172 | Flag2 = {?TYPE, {[], true}}, 173 | Flag3 = {?TYPE, {[{[{1, 2}], enable}], false}}, 174 | Flag4 = {?TYPE, {[{[{1, 3}], disable}], true}}, 175 | ?assertEqual(true, query(Flag0)), 176 | ?assertEqual(true, query(Flag2)), 177 | ?assertEqual(true, query(Flag3)), 178 | ?assertEqual(false, query(Flag4)). 179 | 180 | mutate_test() -> 181 | Flag0 = new(), 182 | {ok, Flag1} = mutate(enable, [{0, 1}, {1, 1}], Flag0), 183 | {ok, Flag2} = mutate(enable, [{0, 2}, {1, 2}], Flag1), 184 | {ok, Flag3} = mutate(enable, [{0, 4}, {1, 1}], Flag2), 185 | {ok, Flag4} = mutate(disable, [{0, 3}, {1, 2}], Flag3), 186 | ?assertEqual({?TYPE, {[{[{0, 1}, {1, 1}], enable}], true}}, Flag1), 187 | ?assertEqual({?TYPE, {[{[{0, 2}, {1, 2}], enable}], true}}, Flag2), 188 | ?assertEqual({?TYPE, {[{[{0, 2}, {1, 2}], enable}, {[{0, 4}, {1, 1}], enable}], true}}, Flag3), 189 | ?assertEqual({?TYPE, {[{[{0, 3}, {1, 2}], disable}], true}}, Flag4). 190 | 191 | reset_test() -> 192 | Flag1 = {?TYPE, {[{[{0, 2}, {1, 2}], enable}, {[{0, 4}, {1, 1}], enable}], false}}, 193 | Flag2 = reset([{0, 5}, {1, 6}], Flag1), 194 | ?assertEqual({?TYPE, {[], true}}, Flag2). 195 | 196 | check_stability_test() -> 197 | Flag0 = new(), 198 | Flag1 = check_stability([], Flag0), 199 | ?assertEqual(Flag0, Flag1). 200 | 201 | equal_test() -> 202 | Flag0 = {?TYPE, {[{[{0, 1}, {1, 1}], enable}], false}}, 203 | Flag1 = {?TYPE, {[{[{0, 1}, {1, 1}], disable}], true}}, 204 | Flag2 = {?TYPE, {[{[{0, 1}, {1, 1}], disable}], true}}, 205 | Flag3 = {?TYPE, {[{[{0, 1}, {1, 1}], enable}], false}}, 206 | ?assert(equal(Flag0, Flag3)), 207 | ?assert(equal(Flag1, Flag2)), 208 | ?assertNot(equal(Flag0, Flag1)), 209 | ?assertNot(equal(Flag2, Flag3)). 210 | 211 | -endif. 212 | -------------------------------------------------------------------------------- /src/pure_ewflag.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure EWFlag CRDT: pure op-based enable-wins flag 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_ewflag). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | -behaviour(pure_polog). 32 | 33 | -define(TYPE, ?MODULE). 34 | 35 | -include("pure_type.hrl"). 36 | 37 | -ifdef(TEST). 38 | -include_lib("eunit/include/eunit.hrl"). 39 | -endif. 40 | 41 | -export([new/0, new/1]). 42 | -export([mutate/3, query/1, equal/2, reset/2]). 43 | -export([redundant/2, remove_redundant_crystal/2, remove_redundant_polog/2, check_stability/2]). 44 | 45 | -export_type([pure_ewflag/0, pure_ewflag_op/0]). 46 | 47 | -opaque pure_ewflag() :: {?TYPE, payload()}. 48 | -type payload() :: {pure_type:polog(), boolean()}. 49 | -type pure_ewflag_op() :: enable | disable. 50 | 51 | %% @doc Create a new, empty `pure_ewflag()' 52 | -spec new() -> pure_ewflag(). 53 | new() -> 54 | {?TYPE, {orddict:new(), false}}. 55 | 56 | %% @doc Create a new, empty `pure_ewflag()' 57 | -spec new([term()]) -> pure_ewflag(). 58 | new([]) -> 59 | new(). 60 | 61 | %% @doc Check redundancy `pure_ewflag()' 62 | %% Called in remove_redundant(). 63 | -spec redundant({pure_type:id(), pure_ewflag_op()}, {pure_type:id(), pure_ewflag_op()}) -> 64 | atom(). 65 | redundant({VV1, Op1}, {VV2, Op2}) -> 66 | case pure_trcb:happened_before(VV1, VV2) of 67 | true -> 68 | ?RA; 69 | false -> 70 | case Op1 == Op2 of 71 | true -> 72 | ?AA; 73 | false -> 74 | case Op2 of 75 | enable -> 76 | ?RA; 77 | disable -> 78 | ?AR 79 | end 80 | end 81 | end. 82 | 83 | %% @doc Removes redundant operations from POLog of `pure_ewflag()' 84 | %% Called upon updating (enable, disable) the `pure_ewflag()' 85 | -spec remove_redundant_polog({pure_type:id(), pure_ewflag_op()}, pure_ewflag()) -> {boolean(), pure_ewflag()}. 86 | remove_redundant_polog({VV1, Op}, {?TYPE, {POLog0, Flag}}) -> 87 | case orddict:is_empty(POLog0) of 88 | true -> 89 | {true, {?TYPE, {POLog0, Flag}}}; 90 | false -> 91 | {POLog1, Add1} = orddict:fold( 92 | fun(Key, Value, {Acc, Add}) -> 93 | case redundant({Key, Value}, {VV1, Op}) of 94 | ?AA -> 95 | {orddict:store(Key, Value, Acc), Add}; 96 | ?RA -> 97 | {Acc, Add}; 98 | ?AR -> 99 | {orddict:store(Key, Value, Acc), Add andalso false} 100 | end 101 | end, 102 | {orddict:new(), true}, 103 | POLog0 104 | ), 105 | {Add1, {?TYPE, {POLog1, Flag}}} 106 | end. 107 | 108 | %% @doc Removes redundant operations from crystal of `pure_ewflag()' 109 | %% No crystalisation for this one. 110 | -spec remove_redundant_crystal({pure_type:id(), pure_ewflag_op()}, pure_ewflag()) -> {boolean(), pure_ewflag()}. 111 | remove_redundant_crystal({_VV1, _X}, {?TYPE, {POLog0, EWFlag}}) -> 112 | {true, {?TYPE, {POLog0, EWFlag}}}. 113 | 114 | %% @doc Checks stable operations and remove them from POLog of `pure_ewflag()' 115 | -spec check_stability(pure_type:id(), pure_ewflag()) -> pure_ewflag(). 116 | check_stability(_StableVV, {?TYPE, {POLog, EWFlag}}) -> 117 | {?TYPE, {POLog, EWFlag}}. 118 | 119 | %% @doc Update a `pure_ewflag()'. 120 | -spec mutate(pure_ewflag_op(), pure_type:id(), pure_ewflag()) -> 121 | {ok, pure_ewflag()}. 122 | mutate(Op, VV, {?TYPE, {POLog, PureEWFlag}}) -> 123 | {Add, {?TYPE, {POLog0, PureEWFlag0}}} = pure_polog:remove_redundant({VV, Op}, {?TYPE, {POLog, PureEWFlag}}), 124 | case Add of 125 | false -> 126 | {ok, {?TYPE, {POLog0, PureEWFlag0}}}; 127 | true -> 128 | {ok, {?TYPE, {orddict:store(VV, Op, POLog0), PureEWFlag0}}} 129 | end. 130 | 131 | %% @doc Clear/reset the state to initial state. 132 | -spec reset(pure_type:id(), pure_ewflag()) -> pure_ewflag(). 133 | reset(VV, {?TYPE, _}=CRDT) -> 134 | pure_type:reset(VV, CRDT). 135 | 136 | %% @doc Returns the value of the `pure_ewflag()'. 137 | %% This value is a a boolean value in the `pure_ewflag()'. 138 | -spec query(pure_ewflag()) -> boolean(). 139 | query({?TYPE, {POLog0, _PureEWFlag0}}) -> 140 | not (orddict:is_empty(POLog0) orelse lists:member(disable, [Op || {_Key, Op} <- orddict:to_list(POLog0)])). 141 | 142 | %% @doc Equality for `pure_ewflag()'. 143 | -spec equal(pure_ewflag(), pure_ewflag()) -> boolean(). 144 | equal({?TYPE, {POLog1, PureEWFlag1}}, {?TYPE, {POLog2, PureEWFlag2}}) -> 145 | Fun = fun(Value1, Value2) -> Value1 == Value2 end, 146 | PureEWFlag1 == PureEWFlag2 andalso orddict_ext:equal(POLog1, POLog2, Fun). 147 | 148 | 149 | %% =================================================================== 150 | %% EUnit tests 151 | %% =================================================================== 152 | -ifdef(TEST). 153 | 154 | new_test() -> 155 | ?assertEqual({?TYPE, {orddict:new(), false}}, new()). 156 | 157 | redundant_test() -> 158 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], enable}, {[{0, 1}, {1, 1}], enable})), 159 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], enable}, {[{0, 1}, {1, 1}], disable})), 160 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], disable}, {[{0, 1}, {1, 1}], enable})), 161 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], disable}, {[{0, 1}, {1, 1}], disable})), 162 | ?assertEqual(?AA, redundant({[{0, 1}, {1, 0}], enable}, {[{0, 0}, {1, 1}], enable})), 163 | ?assertEqual(?AA, redundant({[{0, 1}, {1, 0}], disable}, {[{0, 0}, {1, 1}], disable})), 164 | ?assertEqual(?RA, redundant({[{0, 1}, {1, 0}], disable}, {[{0, 0}, {1, 1}], enable})), 165 | ?assertEqual(?AR, redundant({[{0, 1}, {1, 0}], enable}, {[{0, 0}, {1, 1}], disable})). 166 | 167 | query_test() -> 168 | Flag0 = new(), 169 | Flag1 = {?TYPE, {[], false}}, 170 | Flag3 = {?TYPE, {[{[{1, 2}], enable}], false}}, 171 | Flag4 = {?TYPE, {[{[{1, 3}], disable}], true}}, 172 | ?assertEqual(false, query(Flag0)), 173 | ?assertEqual(false, query(Flag1)), 174 | ?assertEqual(true, query(Flag3)), 175 | ?assertEqual(false, query(Flag4)). 176 | 177 | mutate_test() -> 178 | Flag0 = new(), 179 | {ok, Flag1} = mutate(enable, [{0, 1}, {1, 1}], Flag0), 180 | {ok, Flag2} = mutate(enable, [{0, 2}, {1, 2}], Flag1), 181 | {ok, Flag3} = mutate(enable, [{0, 4}, {1, 1}], Flag2), 182 | {ok, Flag4} = mutate(disable, [{0, 3}, {1, 2}], Flag3), 183 | ?assertEqual({?TYPE, {[{[{0, 1}, {1, 1}], enable}], false}}, Flag1), 184 | ?assertEqual({?TYPE, {[{[{0, 2}, {1, 2}], enable}], false}}, Flag2), 185 | ?assertEqual({?TYPE, {[{[{0, 2}, {1, 2}], enable}, {[{0, 4}, {1, 1}], enable}], false}}, Flag3), 186 | ?assertEqual({?TYPE, {[{[{0, 4}, {1, 1}], enable}], false}}, Flag4). 187 | 188 | reset_test() -> 189 | Flag1 = {?TYPE, {[{[{0, 2}, {1, 2}], enable}, {[{0, 4}, {1, 1}], enable}], true}}, 190 | Flag2 = reset([{0, 5}, {1, 6}], Flag1), 191 | ?assertEqual({?TYPE, {[], false}}, Flag2). 192 | 193 | check_stability_test() -> 194 | Flag0 = new(), 195 | Flag1 = check_stability([], Flag0), 196 | ?assertEqual(Flag0, Flag1). 197 | 198 | equal_test() -> 199 | Flag0 = {?TYPE, {[{[{0, 1}, {1, 1}], enable}], false}}, 200 | Flag1 = {?TYPE, {[{[{0, 1}, {1, 1}], disable}], true}}, 201 | Flag2 = {?TYPE, {[{[{0, 1}, {1, 1}], disable}], true}}, 202 | Flag3 = {?TYPE, {[{[{0, 1}, {1, 1}], enable}], false}}, 203 | ?assert(equal(Flag0, Flag3)), 204 | ?assert(equal(Flag1, Flag2)), 205 | ?assertNot(equal(Flag0, Flag1)), 206 | ?assertNot(equal(Flag2, Flag3)). 207 | 208 | -endif. 209 | -------------------------------------------------------------------------------- /src/pure_gcounter.erl: -------------------------------------------------------------------------------- 1 | % % 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure GCounter CRDT: pure op-based grow-only counter. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_gcounter). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, query/1, equal/2, reset/2]). 40 | 41 | -export_type([pure_gcounter/0, pure_gcounter_op/0]). 42 | 43 | -opaque pure_gcounter() :: {?TYPE, payload()}. 44 | -type payload() :: {pure_type:polog(), integer()}. 45 | -type pure_gcounter_op() :: increment | {increment, non_neg_integer()}. 46 | 47 | %% @doc Create a new, empty `pure_gcounter()' 48 | -spec new() -> pure_gcounter(). 49 | new() -> 50 | {?TYPE, {orddict:new(), 0}}. 51 | 52 | %% @doc Create a new, empty `pure_gcounter()' 53 | -spec new([term()]) -> pure_gcounter(). 54 | new([]) -> 55 | new(). 56 | 57 | %% @doc Update a `pure_gcounter()'. 58 | -spec mutate(pure_gcounter_op(), pure_type:id(), pure_gcounter()) -> 59 | {ok, pure_gcounter()}. 60 | mutate(increment, _VV, {?TYPE, {POLog, PureGCounter}}) -> 61 | PureGCounter1 = {?TYPE, {POLog, PureGCounter + 1}}, 62 | {ok, PureGCounter1}; 63 | mutate({increment, Val}, _VV, {?TYPE, {POLog, PureGCounter}}) -> 64 | PureGCounter1 = {?TYPE, {POLog, PureGCounter + Val}}, 65 | {ok, PureGCounter1}. 66 | 67 | %% @doc Clear/reset the state to initial state. 68 | -spec reset(pure_type:id(), pure_gcounter()) -> pure_gcounter(). 69 | reset(VV, {?TYPE, _}=CRDT) -> 70 | pure_type:reset(VV, CRDT). 71 | 72 | %% @doc Return the value of the `pure_gcounter()'. 73 | -spec query(pure_gcounter()) -> non_neg_integer(). 74 | query({?TYPE, {_POLog, PureGCounter}}) -> 75 | PureGCounter. 76 | 77 | %% @doc Check if two `pure_gcounter()' instances have the same value. 78 | -spec equal(pure_gcounter(), pure_gcounter()) -> boolean(). 79 | equal({?TYPE, {_POLog1, PureGCounter1}}, {?TYPE, {_POLog2, PureGCounter2}}) -> 80 | PureGCounter1 == PureGCounter2. 81 | 82 | %% =================================================================== 83 | %% EUnit tests 84 | %% =================================================================== 85 | -ifdef(TEST). 86 | 87 | new_test() -> 88 | ?assertEqual({?TYPE, {[], 0}}, new()). 89 | 90 | query_test() -> 91 | PureGCounter0 = new(), 92 | PureGCounter1 = {?TYPE, {[], 15}}, 93 | ?assertEqual(0, query(PureGCounter0)), 94 | ?assertEqual(15, query(PureGCounter1)). 95 | 96 | increment_test() -> 97 | PureGCounter0 = new(), 98 | {ok, PureGCounter1} = mutate(increment, [], PureGCounter0), 99 | {ok, PureGCounter2} = mutate(increment, [], PureGCounter1), 100 | {ok, PureGCounter3} = mutate({increment, 5}, [], PureGCounter2), 101 | ?assertEqual({?TYPE, {[], 1}}, PureGCounter1), 102 | ?assertEqual({?TYPE, {[], 2}}, PureGCounter2), 103 | ?assertEqual({?TYPE, {[], 7}}, PureGCounter3). 104 | 105 | reset_test() -> 106 | PureGCounter1 = {?TYPE, {[], 15}}, 107 | PureGCounter2 = reset([], PureGCounter1), 108 | ?assertEqual({?TYPE, {[], 0}}, PureGCounter2). 109 | 110 | equal_test() -> 111 | PureGCounter1 = {?TYPE, {[], 1}}, 112 | PureGCounter2 = {?TYPE, {[], 2}}, 113 | PureGCounter3 = {?TYPE, {[], 3}}, 114 | ?assert(equal(PureGCounter1, PureGCounter1)), 115 | ?assertNot(equal(PureGCounter2, PureGCounter1)), 116 | ?assertNot(equal(PureGCounter2, PureGCounter3)). 117 | 118 | -endif. 119 | -------------------------------------------------------------------------------- /src/pure_gset.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure GSet CRDT: pure op-based grow-only set. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_gset). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, query/1, equal/2, reset/2]). 40 | 41 | -export_type([pure_gset/0, pure_gset_op/0]). 42 | 43 | -opaque pure_gset() :: {?TYPE, payload()}. 44 | -type payload() :: {pure_type:polog(), ordsets:ordset(any())}. 45 | -type pure_gset_op() :: {add, pure_type:element()}. 46 | 47 | %% @doc Create a new, empty `pure_gset()' 48 | -spec new() -> pure_gset(). 49 | new() -> 50 | {?TYPE, {orddict:new(), ordsets:new()}}. 51 | 52 | %% @doc Create a new, empty `pure_gset()' 53 | -spec new([term()]) -> pure_gset(). 54 | new([]) -> 55 | new(). 56 | 57 | %% @doc Update a `pure_gset()'. 58 | -spec mutate(pure_gset_op(), pure_type:id(), pure_gset()) -> 59 | {ok, pure_gset()}. 60 | mutate({add, Elem}, _VV, {?TYPE, {POLog, PureGSet}}) -> 61 | PureGSet1 = {?TYPE, {POLog, ordsets:add_element(Elem, PureGSet)}}, 62 | {ok, PureGSet1}. 63 | 64 | %% @doc Clear/reset the state to initial state. 65 | -spec reset(pure_type:id(), pure_gset()) -> pure_gset(). 66 | reset(VV, {?TYPE, _}=CRDT) -> 67 | pure_type:reset(VV, CRDT). 68 | 69 | %% @doc Returns the value of the `pure_gset()'. 70 | %% This value is a set with all the elements in the `pure_gset()'. 71 | -spec query(pure_gset()) -> sets:set(pure_type:element()). 72 | query({?TYPE, {_, PureGSet}}) -> 73 | sets:from_list(PureGSet). 74 | 75 | %% @doc Equality for `pure_gset()'. 76 | -spec equal(pure_gset(), pure_gset()) -> boolean(). 77 | equal({?TYPE, {_, PureGSet1}}, {?TYPE, {_, PureGSet2}}) -> 78 | ordsets_ext:equal(PureGSet1, PureGSet2). 79 | 80 | %% =================================================================== 81 | %% EUnit tests 82 | %% =================================================================== 83 | -ifdef(TEST). 84 | 85 | new_test() -> 86 | ?assertEqual({?TYPE, {orddict:new(), ordsets:new()}}, new()). 87 | 88 | query_test() -> 89 | Set0 = new(), 90 | Set1 = {?TYPE, {[], [<<"a">>]}}, 91 | ?assertEqual(sets:new(), query(Set0)), 92 | ?assertEqual(sets:from_list([<<"a">>]), query(Set1)). 93 | 94 | add_test() -> 95 | Set0 = new(), 96 | {ok, Set1} = mutate({add, <<"a">>}, [], Set0), 97 | {ok, Set2} = mutate({add, <<"b">>}, [], Set1), 98 | ?assertEqual({?TYPE, {[], [<<"a">>]}}, Set1), 99 | ?assertEqual({?TYPE, {[], [<<"a">>, <<"b">>]}}, Set2). 100 | 101 | reset_test() -> 102 | Set1 = {?TYPE, {[], [<<"a">>, <<"b">>]}}, 103 | Set2 = reset([], Set1), 104 | ?assertEqual({?TYPE, {[], []}}, Set2). 105 | 106 | equal_test() -> 107 | Set1 = {?TYPE, {[], [<<"a">>]}}, 108 | Set2 = {?TYPE, {[], [<<"a">>, <<"b">>]}}, 109 | ?assert(equal(Set1, Set1)), 110 | ?assertNot(equal(Set1, Set2)). 111 | 112 | -endif. 113 | -------------------------------------------------------------------------------- /src/pure_mvregister.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure MVRegister CRDT: pure op-based multi-value register 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_mvregister). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | -behaviour(pure_polog). 32 | 33 | -define(TYPE, ?MODULE). 34 | 35 | -include("pure_type.hrl"). 36 | 37 | -ifdef(TEST). 38 | -include_lib("eunit/include/eunit.hrl"). 39 | -endif. 40 | 41 | -export([new/0, new/1]). 42 | -export([mutate/3, query/1, equal/2, reset/2]). 43 | -export([redundant/2, remove_redundant_crystal/2, remove_redundant_polog/2, check_stability/2]). 44 | 45 | -export_type([pure_mvregister/0, pure_mvregister_op/0]). 46 | 47 | -opaque pure_mvregister() :: {?TYPE, payload()}. 48 | -type payload() :: {pure_type:polog(), []}. 49 | -type timestamp() :: non_neg_integer(). 50 | -type value() :: term(). 51 | -type pure_mvregister_op() :: {set, timestamp(), value()}. 52 | 53 | %% @doc Create a new, empty `pure_mvregister()' 54 | -spec new() -> pure_mvregister(). 55 | new() -> 56 | {?TYPE, {orddict:new(), []}}. 57 | 58 | %% @doc Create a new, empty `pure_mvregister()' 59 | -spec new([term()]) -> pure_mvregister(). 60 | new([]) -> 61 | new(). 62 | 63 | %% @doc Check redundancy `pure_mvregister()' 64 | %% Called in remove_redundant(). 65 | -spec redundant({pure_type:id(), pure_mvregister_op()}, {pure_type:id(), pure_mvregister_op()}) -> 66 | atom(). 67 | redundant({VV1, _Op1}, {VV2, _Op2}) -> 68 | case pure_trcb:happened_before(VV1, VV2) of 69 | true -> 70 | ?RA; 71 | false -> 72 | ?AA 73 | end. 74 | 75 | %% @doc Removes redundant operations from POLog of `pure_mvregister()' 76 | -spec remove_redundant_polog({pure_type:id(), pure_mvregister_op()}, pure_mvregister()) -> {boolean(), pure_mvregister()}. 77 | remove_redundant_polog({VV1, Op}, {?TYPE, {POLog0, MVReg}}) -> 78 | case orddict:is_empty(POLog0) of 79 | true -> 80 | {true, {?TYPE, {POLog0, MVReg}}}; 81 | false -> 82 | {POLog1, Add1} = orddict:fold( 83 | fun(Key, Value, {Acc, Add}) -> 84 | Timestamp = 0, %% won't be used 85 | case redundant({Key, {set, Timestamp, Value}}, {VV1, Op}) of 86 | ?AA -> 87 | {orddict:store(Key, Value, Acc), Add}; 88 | ?RA -> 89 | {Acc, Add} 90 | end 91 | end, 92 | {orddict:new(), true}, 93 | POLog0 94 | ), 95 | {Add1, {?TYPE, {POLog1, MVReg}}} 96 | end. 97 | 98 | %% @doc Removes redundant operations from crystal of `pure_mvregister()' 99 | %% No crystalisation for this one. 100 | -spec remove_redundant_crystal({pure_type:id(), pure_mvregister_op()}, pure_mvregister()) -> {boolean(), pure_mvregister()}. 101 | remove_redundant_crystal({_VV1, _X}, {?TYPE, {POLog0, MVReg}}) -> 102 | {true, {?TYPE, {POLog0, MVReg}}}. 103 | 104 | %% @doc Checks stable operations and remove them from POLog of `pure_mvregister()' 105 | -spec check_stability(pure_type:id(), pure_mvregister()) -> pure_mvregister(). 106 | check_stability(_StableVV, {?TYPE, {POLog0, MVReg0}}) -> 107 | {?TYPE, {POLog0, MVReg0}}. 108 | 109 | %% @doc Update a `pure_mvregister()'. 110 | %% The first argument is: 111 | %% - `{set, timestamp(), value()}'. 112 | %% - the second component in this triple will not be used. 113 | %% - in order to have an unified API for all registers 114 | %% (since LWWRegister needs to receive a timestamp), 115 | %% the timestamp is also supplied here 116 | -spec mutate(pure_mvregister_op(), pure_type:id(), pure_mvregister()) -> 117 | {ok, pure_mvregister()}. 118 | mutate({Op, _Timestamp, Value}, VV, {?TYPE, {POLog, PureMVReg}}) -> 119 | {_Add, {?TYPE, {POLog0, PureMVReg0}}} = pure_polog:remove_redundant({VV, {Op, Value}}, {?TYPE, {POLog, PureMVReg}}), 120 | {ok, {?TYPE, {orddict:store(VV, Value, POLog0), PureMVReg0}}}. 121 | 122 | %% @doc Clear/reset the state to initial state. 123 | -spec reset(pure_type:id(), pure_mvregister()) -> pure_mvregister(). 124 | reset(VV, {?TYPE, _}=CRDT) -> 125 | pure_type:reset(VV, CRDT). 126 | 127 | %% @doc Returns the value of the `pure_mvregister()'. 128 | -spec query(pure_mvregister()) -> sets:set(value()). 129 | query({?TYPE, {POLog0, _PureMVReg0}}) -> 130 | sets:from_list([Value || {_Key, Value} <- orddict:to_list(POLog0)]). 131 | 132 | %% @doc Equality for `pure_mvregister()'. 133 | -spec equal(pure_mvregister(), pure_mvregister()) -> boolean(). 134 | equal({?TYPE, {POLog1, _PureMVReg1}}, {?TYPE, {POLog2, _PureMVReg2}}) -> 135 | Fun = fun(Value1, Value2) -> Value1 == Value2 end, 136 | orddict_ext:equal(POLog1, POLog2, Fun). 137 | 138 | 139 | %% =================================================================== 140 | %% EUnit tests 141 | %% =================================================================== 142 | -ifdef(TEST). 143 | 144 | new_test() -> 145 | ?assertEqual({?TYPE, {orddict:new(), []}}, new()). 146 | 147 | redundant_test() -> 148 | Timestamp = 0, %% won't be used 149 | ?assertEqual(?RA, redundant({[{0, 0}, {1, 0}], {set, Timestamp, "foo"}}, {[{0, 1}, {1, 1}], {set, Timestamp, "bar"}})), 150 | ?assertEqual(?AA, redundant({[{0, 0}, {1, 1}], {set, Timestamp, "foo"}}, {[{0, 1}, {1, 0}], {set, Timestamp, "bar"}})). 151 | 152 | query_test() -> 153 | MVReg0 = new(), 154 | MVReg1 = {?TYPE, {[{[{1, 2}], "foo"}], []}}, 155 | MVReg2 = {?TYPE, {[{[{0, 1}, {1, 3}], "bar"}, {[{0, 2}, {1, 1}], "baz"}], []}}, 156 | ?assertEqual(sets:from_list([]), query(MVReg0)), 157 | ?assertEqual(sets:from_list(["foo"]), query(MVReg1)), 158 | ?assertEqual(sets:from_list(["bar", "baz"]), query(MVReg2)). 159 | 160 | mutate_test() -> 161 | Timestamp = 0, %% won't be used 162 | MVReg0 = new(), 163 | {ok, MVReg1} = mutate({set, Timestamp, "foo"}, [{0, 1}, {1, 1}], MVReg0), 164 | {ok, MVReg2} = mutate({set, Timestamp, "bar"}, [{0, 2}, {1, 2}], MVReg1), 165 | ?assertEqual({?TYPE, {[{[{0, 1}, {1, 1}], "foo"}], []}}, MVReg1), 166 | ?assertEqual({?TYPE, {[{[{0, 2}, {1, 2}], "bar"}], []}}, MVReg2). 167 | 168 | reset_test() -> 169 | MVReg1 = {?TYPE, {[{[{0, 1}, {1, 3}], "bar"}, {[{0, 2}, {1, 1}], "baz"}], []}}, 170 | MVReg2 = reset([{0, 5}, {1, 6}], MVReg1), 171 | ?assertEqual({?TYPE, {[], []}}, MVReg2). 172 | 173 | check_stability_test() -> 174 | MVReg0 = new(), 175 | MVReg1 = check_stability([], MVReg0), 176 | ?assertEqual(MVReg0, MVReg1). 177 | 178 | equal_test() -> 179 | MVReg0 = {?TYPE, {[{[{0, 1}, {1, 1}], "foo"}], []}}, 180 | MVReg1 = {?TYPE, {[{[{0, 1}, {1, 1}], "bar"}], []}}, 181 | MVReg2 = {?TYPE, {[{[{0, 1}, {1, 1}], "bar"}], []}}, 182 | MVReg3 = {?TYPE, {[{[{0, 1}, {1, 1}], "foo"}], []}}, 183 | ?assert(equal(MVReg0, MVReg3)), 184 | ?assert(equal(MVReg1, MVReg2)), 185 | ?assertNot(equal(MVReg0, MVReg1)), 186 | ?assertNot(equal(MVReg2, MVReg3)). 187 | 188 | -endif. 189 | -------------------------------------------------------------------------------- /src/pure_pncounter.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure PNCounter CRDT: pure op-based grow-only counter. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_pncounter). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, query/1, equal/2, reset/2]). 40 | 41 | -export_type([pure_pncounter/0, pure_pncounter_op/0]). 42 | 43 | -opaque pure_pncounter() :: {?TYPE, payload()}. 44 | -type payload() :: {pure_type:polog(), integer()}. 45 | -type pure_pncounter_op() :: increment | decrement | {increment, integer()} | {decrement, integer()}. 46 | 47 | %% @doc Create a new, empty `pure_pncounter()' 48 | -spec new() -> pure_pncounter(). 49 | new() -> 50 | {?TYPE, {orddict:new(), 0}}. 51 | 52 | %% @doc Create a new, empty `pure_pncounter()' 53 | -spec new([term()]) -> pure_pncounter(). 54 | new([]) -> 55 | new(). 56 | 57 | %% @doc Update a `pure_pncounter()'. 58 | -spec mutate(pure_pncounter_op(), pure_type:id(), pure_pncounter()) -> 59 | {ok, pure_pncounter()}. 60 | mutate(increment, _VV, {?TYPE, {POLog, PurePNCounter}}) -> 61 | PurePNCounter1 = {?TYPE, {POLog, PurePNCounter + 1}}, 62 | {ok, PurePNCounter1}; 63 | mutate({increment, Val}, _VV, {?TYPE, {POLog, PurePNCounter}}) -> 64 | PurePNCounter1 = {?TYPE, {POLog, PurePNCounter + Val}}, 65 | {ok, PurePNCounter1}; 66 | mutate(decrement, _VV, {?TYPE, {POLog, PurePNCounter}}) -> 67 | PurePNCounter1 = {?TYPE, {POLog, PurePNCounter - 1}}, 68 | {ok, PurePNCounter1}; 69 | mutate({decrement, Val}, _VV, {?TYPE, {POLog, PurePNCounter}}) -> 70 | PurePNCounter1 = {?TYPE, {POLog, PurePNCounter - Val}}, 71 | {ok, PurePNCounter1}. 72 | 73 | %% @doc Clear/reset the state to initial state. 74 | -spec reset(pure_type:id(), pure_pncounter()) -> pure_pncounter(). 75 | reset(VV, {?TYPE, _}=CRDT) -> 76 | pure_type:reset(VV, CRDT). 77 | 78 | %% @doc Return the value of the `pure_pncounter()'. 79 | -spec query(pure_pncounter()) -> integer(). 80 | query({?TYPE, {_POLog, PurePNCounter}}) -> 81 | PurePNCounter. 82 | 83 | %% @doc Check if two `pure_pncounter()' instances have the same value. 84 | -spec equal(pure_pncounter(), pure_pncounter()) -> boolean(). 85 | equal({?TYPE, {_POLog1, PurePNCounter1}}, {?TYPE, {_POLog2, PurePNCounter2}}) -> 86 | PurePNCounter1 == PurePNCounter2. 87 | 88 | %% =================================================================== 89 | %% EUnit tests 90 | %% =================================================================== 91 | -ifdef(TEST). 92 | 93 | new_test() -> 94 | ?assertEqual({?TYPE, {[], 0}}, new()). 95 | 96 | query_test() -> 97 | PurePNCounter0 = new(), 98 | PurePNCounter1 = {?TYPE, {[], 15}}, 99 | ?assertEqual(0, query(PurePNCounter0)), 100 | ?assertEqual(15, query(PurePNCounter1)). 101 | 102 | increment_test() -> 103 | PurePNCounter0 = new(), 104 | {ok, PurePNCounter1} = mutate(increment, [], PurePNCounter0), 105 | {ok, PurePNCounter2} = mutate(increment, [], PurePNCounter1), 106 | {ok, PurePNCounter3} = mutate({increment, 5}, [], PurePNCounter2), 107 | ?assertEqual({?TYPE, {[], 1}}, PurePNCounter1), 108 | ?assertEqual({?TYPE, {[], 2}}, PurePNCounter2), 109 | ?assertEqual({?TYPE, {[], 7}}, PurePNCounter3). 110 | 111 | decrement_test() -> 112 | PurePNCounter0 = {?TYPE, {[], 1}}, 113 | {ok, PurePNCounter1} = mutate(decrement, [], PurePNCounter0), 114 | {ok, PurePNCounter2} = mutate(decrement, [], PurePNCounter1), 115 | {ok, PurePNCounter3} = mutate({decrement, 5}, [], PurePNCounter2), 116 | {ok, PurePNCounter4} = mutate({decrement, -8}, [], PurePNCounter3), 117 | ?assertEqual({?TYPE, {[], 0}}, PurePNCounter1), 118 | ?assertEqual({?TYPE, {[], -1}}, PurePNCounter2), 119 | ?assertEqual({?TYPE, {[], -6}}, PurePNCounter3), 120 | ?assertEqual({?TYPE, {[], 2}}, PurePNCounter4). 121 | 122 | reset_test() -> 123 | PurePNCounter1 = {?TYPE, {[], 54}}, 124 | PurePNCounter2 = reset([], PurePNCounter1), 125 | ?assertEqual({?TYPE, {[], 0}}, PurePNCounter2). 126 | 127 | equal_test() -> 128 | PurePNCounter1 = {?TYPE, {[], 1}}, 129 | PurePNCounter2 = {?TYPE, {[], 2}}, 130 | PurePNCounter3 = {?TYPE, {[], 3}}, 131 | ?assert(equal(PurePNCounter1, PurePNCounter1)), 132 | ?assertNot(equal(PurePNCounter2, PurePNCounter1)), 133 | ?assertNot(equal(PurePNCounter2, PurePNCounter3)). 134 | 135 | -endif. 136 | -------------------------------------------------------------------------------- /src/pure_polog.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc POLog: Partially Ordered Log 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_polog). 27 | -author("Georges Younes "). 28 | 29 | -export([remove_redundant/2]). 30 | 31 | %% @doc Check redundant operations. 32 | -callback redundant({pure_type:id(), type:operation()}, {pure_type:id(), type:operation()}) -> 33 | atom(). 34 | 35 | %% @doc Remove redundant operations from the crystal. 36 | -callback remove_redundant_crystal({pure_type:id(), type:operation()}, type:crdt()) -> {boolean(), type:crdt()}. 37 | 38 | %% @doc Remove redundant operations from the POLog. 39 | -callback remove_redundant_polog({pure_type:id(), type:operation()}, type:crdt()) -> {boolean(), type:crdt()}. 40 | 41 | %% @doc Check stable operations and move them from the POLog to the crystal 42 | -callback check_stability(pure_type:id(), type:crdt()) -> type:crdt(). 43 | 44 | %% @doc Remove redundant operations. 45 | -spec remove_redundant({pure_type:id(), type:operation()}, type:crdt()) -> {boolean(), type:crdt()}. 46 | remove_redundant({VV1, Op}, {Type, {POLog, Crystal}}) -> 47 | {Add0, {Type, {POLog0, Crystal0}}} = Type:remove_redundant_crystal({VV1, Op}, {Type, {POLog, Crystal}}), 48 | {Add1, {Type, {POLog1, Crystal1}}} = Type:remove_redundant_polog({VV1, Op}, {Type, {POLog0, Crystal0}}), 49 | {Add0 andalso Add1, {Type, {POLog1, Crystal1}}}. 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/pure_trcb.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc initial TRCB lib 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_trcb). 27 | -author("Georges Younes "). 28 | 29 | -ifdef(TEST). 30 | -include_lib("eunit/include/eunit.hrl"). 31 | -endif. 32 | 33 | -export([happened_before/2]). 34 | 35 | %% @doc Check causal order of 2 version vector. 36 | -spec happened_before(orddict:orddict(), orddict:orddict()) -> boolean(). 37 | happened_before(VV1, VV2) -> 38 | Fun = fun(Value1, Value2) -> Value1 == Value2 end, 39 | orddict:size(VV1) == orddict:size(VV2) andalso 40 | orddict:fold( 41 | fun(Key, Value1, Acc) -> 42 | case orddict:find(Key, VV2) of 43 | {ok, Value2} -> 44 | Acc andalso Value1 =< Value2; 45 | error -> 46 | Acc andalso false 47 | end 48 | end, 49 | true, 50 | VV1 51 | ) andalso not (orddict_ext:equal(VV1, VV2, Fun)). 52 | % to-do: replace happend before wth this 53 | % Fun = fun(Value1, Value2) -> Value1 == Value2 end, 54 | % orddict:size(VV1) == orddict:size(VV2) andalso 55 | % lists_ext:iterate_until( 56 | % fun({Key, Value1}) -> 57 | % case orddict:find(Key, VV2) of 58 | % {ok, Value2} -> 59 | % Value1 =< Value2; 60 | % error -> 61 | % false 62 | % end 63 | % end, 64 | % VV1 65 | % ) andalso not (orddict_ext:equal(VV1, VV2, Fun)). 66 | 67 | %% =================================================================== 68 | %% EUnit tests 69 | %% =================================================================== 70 | -ifdef(TEST). 71 | 72 | happened_before_test() -> 73 | ?assertEqual(false, happened_before([{0, 1}, {1, 2}, {2, 3}], [{0, 1}, {1, 2}, {2, 3}])), 74 | ?assertEqual(true, happened_before([{0, 1}, {1, 2}, {2, 3}], [{0, 2}, {1, 4}, {2, 5}])), 75 | ?assertEqual(false, happened_before([{0, 1}, {1, 2}, {2, 3}], [{0, 1}, {1, 4}, {2, 2}])), 76 | ?assertEqual(false, happened_before([{0, 2}, {1, 5}, {2, 3}], [{0, 1}, {1, 4}, {2, 2}])), 77 | ?assertEqual(true, happened_before([{0, 1}, {1, 2}, {2, 3}], [{0, 1}, {1, 4}, {2, 3}])). 78 | 79 | -endif. 80 | -------------------------------------------------------------------------------- /src/pure_twopset.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Pure twoPSet CRDT: pure op-based two-phase set. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, and Ali Shoker 23 | %% Making Operation-based CRDTs Operation-based (2014) 24 | %% [http://haslab.uminho.pt/ashoker/files/opbaseddais14.pdf] 25 | 26 | -module(pure_twopset). 27 | -author("Georges Younes "). 28 | 29 | -behaviour(type). 30 | -behaviour(pure_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, query/1, equal/2, reset/2]). 40 | 41 | -export_type([pure_twopset/0, pure_twopset_op/0]). 42 | 43 | -opaque pure_twopset() :: {?TYPE, payload()}. 44 | -type payload() :: {pure_type:polog(), {ordsets:ordset(any()), ordsets:ordset(any())}}. 45 | -type pure_twopset_op() :: {add, pure_type:element()} | {rmv, pure_type:element()}. 46 | 47 | %% @doc Create a new, empty `pure_twopset()' 48 | -spec new() -> pure_twopset(). 49 | new() -> 50 | {?TYPE, {orddict:new(), {ordsets:new(), ordsets:new()}}}. 51 | 52 | %% @doc Create a new, empty `pure_twopset()' 53 | -spec new([term()]) -> pure_twopset(). 54 | new([]) -> 55 | new(). 56 | 57 | %% @doc Update a `pure_twopset()'. 58 | -spec mutate(pure_twopset_op(), pure_type:id(), pure_twopset()) -> 59 | {ok, pure_twopset()}. 60 | mutate({add, Elem}, _VV, {?TYPE, {POLog, {Pure2PAddSet, Pure2PRmvSet}}}) -> 61 | AlreadyRemoved = ordsets:is_element(Elem, Pure2PRmvSet), 62 | case AlreadyRemoved of 63 | true -> 64 | {ok, {?TYPE, {POLog, Pure2PAddSet, Pure2PRmvSet}}}; 65 | false -> 66 | PureTwoPSet = {?TYPE, {POLog, {ordsets:add_element(Elem, Pure2PAddSet), Pure2PRmvSet}}}, 67 | {ok, PureTwoPSet} 68 | end; 69 | mutate({rmv, Elem}, _VV, {?TYPE, {POLog, {Pure2PAddSet, Pure2PRmvSet}}}) -> 70 | PureTwoPSet = {?TYPE, {POLog, {ordsets:del_element(Elem, Pure2PAddSet), ordsets:add_element(Elem, Pure2PRmvSet)}}}, 71 | {ok, PureTwoPSet}. 72 | 73 | %% @doc Clear/reset the state to initial state. 74 | -spec reset(pure_type:id(), pure_twopset()) -> pure_twopset(). 75 | reset(VV, {?TYPE, _}=CRDT) -> 76 | pure_type:reset(VV, CRDT). 77 | 78 | %% @doc Returns the value of the `pure_twopset()'. 79 | %% This value is a set with all the elements in the `pure_twopset()'. 80 | -spec query(pure_twopset()) -> sets:set(pure_type:element()). 81 | query({?TYPE, {_POLog, {Pure2PAddSet, Pure2PRmvSet}}}) -> 82 | sets:from_list(ordsets:subtract(Pure2PAddSet, Pure2PRmvSet)). 83 | 84 | %% @doc Equality for `pure_twopset()'. 85 | %% @todo use ordsets_ext:equal instead 86 | -spec equal(pure_twopset(), pure_twopset()) -> boolean(). 87 | equal({?TYPE, {_POLog1, {Pure2PAddSet1, Pure2PRmvSet1}}}, {?TYPE, {_POLog2, {Pure2PAddSet2, Pure2PRmvSet2}}}) -> 88 | ordsets_ext:equal(Pure2PAddSet1, Pure2PAddSet2) andalso 89 | ordsets_ext:equal(Pure2PRmvSet1, Pure2PRmvSet2). 90 | %% =================================================================== 91 | %% EUnit tests 92 | %% =================================================================== 93 | -ifdef(TEST). 94 | 95 | new_test() -> 96 | ?assertEqual({?TYPE, {orddict:new(), {ordsets:new(), ordsets:new()}}}, new()). 97 | 98 | query_test() -> 99 | Set0 = new(), 100 | Set1 = {?TYPE, {[], {[<<"a">>], []}}}, 101 | Set2 = {?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 102 | ?assertEqual(sets:new(), query(Set0)), 103 | ?assertEqual(sets:from_list([<<"a">>]), query(Set1)), 104 | ?assertEqual(sets:from_list([<<"b">>]), query(Set2)). 105 | 106 | add_test() -> 107 | Set0 = new(), 108 | {ok, Set1} = mutate({add, <<"a">>}, [], Set0), 109 | {ok, Set2} = mutate({add, <<"b">>}, [], Set1), 110 | ?assertEqual({?TYPE, {[], {[<<"a">>], []}}}, Set1), 111 | ?assertEqual({?TYPE, {[], {[<<"a">>, <<"b">>], []}}}, Set2). 112 | 113 | rmv_test() -> 114 | Set0 = {?TYPE, {[], {[<<"a">>, <<"b">>, <<"c">>], []}}}, 115 | Set1 = {?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 116 | {ok, Set2} = mutate({rmv, <<"a">>}, [], Set0), 117 | {ok, Set3} = mutate({rmv, <<"c">>}, [], Set1), 118 | ?assertEqual({?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>]}}}, Set2), 119 | ?assertEqual({?TYPE, {[], {[<<"b">>], [<<"a">>, <<"c">>]}}}, Set3). 120 | 121 | reset_test() -> 122 | Set1 = {?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 123 | Set2 = reset([], Set1), 124 | ?assertEqual({?TYPE, {[], {[], []}}}, Set2). 125 | 126 | equal_test() -> 127 | Set0 = {?TYPE, {[], {[<<"a">>, <<"b">>, <<"c">>], []}}}, 128 | Set1 = {?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 129 | Set2 = {?TYPE, {[], {[<<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 130 | Set3 = {?TYPE, {[], {[<<"a">>, <<"b">>, <<"c">>], [<<"a">>, <<"c">>]}}}, 131 | ?assert(equal(Set1, Set2)), 132 | ?assertNot(equal(Set0, Set1)), 133 | ?assertNot(equal(Set2, Set3)). 134 | 135 | -endif. 136 | -------------------------------------------------------------------------------- /src/pure_type.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Inspired by both the Lasp data type behaviour and the Riak data 21 | %% type behaviour. 22 | 23 | -module(pure_type). 24 | -author("Georges Younes "). 25 | 26 | -export_type([pure_type/0, 27 | crdt/0, 28 | polog/0, 29 | id/0, 30 | element/0]). 31 | 32 | -export([reset/2]). 33 | 34 | %% Define some initial types. 35 | -type pure_type() :: pure_awset | 36 | pure_dwflag | 37 | pure_ewflag | 38 | pure_gcounter | 39 | pure_gset | 40 | pure_mvregister | 41 | pure_pncounter | 42 | pure_rwset | 43 | pure_twopset. 44 | -type crdt() :: {pure_type(), payload()}. 45 | -type payload() :: {polog(), term()}. 46 | -type polog() :: orddict:orddict(). 47 | -type id() :: orddict:orddict(). 48 | -type element() :: term(). 49 | 50 | %% Reset the data type 51 | -callback reset(pure_type:id(), crdt()) -> crdt(). 52 | 53 | %% @doc Clear/reset the state to initial state. 54 | -spec reset(pure_type:id(), crdt()) -> crdt(). 55 | reset(VV, {Type, {POLog, _Crystal}}) -> 56 | {Type, {_POLog, Crystal}} = Type:new(), 57 | POLog1 = orddict:filter( 58 | fun(VV1, _Op) -> 59 | not pure_trcb:happened_before(VV1, VV) 60 | end, 61 | POLog 62 | ), 63 | {Type, {POLog1, Crystal}}. 64 | -------------------------------------------------------------------------------- /src/state_boolean.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Boolean primitive CRDT. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, Alcino Cunha and Carla Ferreira 23 | %% Composition of State-based CRDTs (2015) 24 | %% [http://haslab.uminho.pt/cbm/files/crdtcompositionreport.pdf] 25 | 26 | -module(state_boolean). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -behaviour(type). 30 | -behaviour(state_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, delta_mutate/3, merge/2]). 40 | -export([query/1, equal/2, is_bottom/1, 41 | is_inflation/2, is_strict_inflation/2, 42 | irreducible_is_strict_inflation/2]). 43 | -export([join_decomposition/1, delta/2, digest/1]). 44 | -export([encode/2, decode/2]). 45 | 46 | -export_type([state_boolean/0, state_boolean_op/0]). 47 | 48 | -opaque state_boolean() :: {?TYPE, payload()}. 49 | -type payload() :: 0 | 1. 50 | -type state_boolean_op() :: true. 51 | 52 | %% @doc Create a new `state_boolean()' 53 | -spec new() -> state_boolean(). 54 | new() -> 55 | {?TYPE, 0}. 56 | 57 | %% @doc Create a new `state_boolean()' 58 | -spec new([term()]) -> state_boolean(). 59 | new([]) -> 60 | new(). 61 | 62 | %% @doc Mutate a `state_boolean()'. 63 | -spec mutate(state_boolean_op(), type:id(), state_boolean()) -> 64 | {ok, state_boolean()}. 65 | mutate(Op, Actor, {?TYPE, _Boolean}=CRDT) -> 66 | state_type:mutate(Op, Actor, CRDT). 67 | 68 | %% @doc Delta-mutate a `state_boolean()'. 69 | %% The first argument can only be `true'. 70 | %% The second argument is the replica id. 71 | %% The third argument is the `state_boolean()' to be inflated. 72 | -spec delta_mutate(state_boolean_op(), type:id(), state_boolean()) -> 73 | {ok, state_boolean()}. 74 | delta_mutate(true, _Actor, {?TYPE, _Boolean}) -> 75 | {ok, {?TYPE, 1}}. 76 | 77 | %% @doc Returns the value of the `state_boolean()'. 78 | -spec query(state_boolean()) -> boolean(). 79 | query({?TYPE, Boolean}) -> 80 | Boolean == 1. 81 | 82 | %% @doc Merge two `state_boolean()'. 83 | %% Join is the logical or. 84 | -spec merge(state_boolean(), state_boolean()) -> state_boolean(). 85 | merge({?TYPE, Boolean1}, {?TYPE, Boolean2}) -> 86 | {?TYPE, max(Boolean1, Boolean2)}. 87 | 88 | %% @doc Equality for `state_boolean()'. 89 | -spec equal(state_boolean(), state_boolean()) -> boolean(). 90 | equal({?TYPE, Boolean1}, {?TYPE, Boolean2}) -> 91 | Boolean1 == Boolean2. 92 | 93 | %% @doc Check if a Boolean is bottom. 94 | -spec is_bottom(state_boolean()) -> boolean(). 95 | is_bottom({?TYPE, Boolean}) -> 96 | Boolean == 0. 97 | 98 | %% @doc Given two `state_boolean()', check if the second is an inflation 99 | %% of the first. 100 | -spec is_inflation(state_boolean(), state_boolean()) -> boolean(). 101 | is_inflation({?TYPE, Boolean1}, {?TYPE, Boolean2}) -> 102 | Boolean1 == Boolean2 orelse 103 | (Boolean1 == 0 andalso Boolean2 == 1). 104 | 105 | %% @doc Check for strict inflation. 106 | -spec is_strict_inflation(state_boolean(), state_boolean()) -> boolean(). 107 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 108 | state_type:is_strict_inflation(CRDT1, CRDT2). 109 | 110 | %% @doc Check for irreducible strict inflation. 111 | -spec irreducible_is_strict_inflation(state_boolean(), 112 | state_type:digest()) -> 113 | boolean(). 114 | irreducible_is_strict_inflation({?TYPE, _}=A, B) -> 115 | state_type:irreducible_is_strict_inflation(A, B). 116 | 117 | -spec digest(state_boolean()) -> state_type:digest(). 118 | digest({?TYPE, _}=CRDT) -> 119 | {state, CRDT}. 120 | 121 | %% @doc Join decomposition for `state_boolean()'. 122 | -spec join_decomposition(state_boolean()) -> [state_boolean()]. 123 | join_decomposition({?TYPE, _}=Boolean) -> 124 | [Boolean]. 125 | 126 | %% @doc Delta calculation for `state_boolean()'. 127 | -spec delta(state_boolean(), state_type:digest()) -> state_boolean(). 128 | delta({?TYPE, _}=A, B) -> 129 | state_type:delta(A, B). 130 | 131 | -spec encode(state_type:format(), state_boolean()) -> binary(). 132 | encode(erlang, {?TYPE, _}=CRDT) -> 133 | erlang:term_to_binary(CRDT). 134 | 135 | -spec decode(state_type:format(), binary()) -> state_boolean(). 136 | decode(erlang, Binary) -> 137 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 138 | CRDT. 139 | 140 | 141 | %% =================================================================== 142 | %% EUnit tests 143 | %% =================================================================== 144 | -ifdef(TEST). 145 | 146 | new_test() -> 147 | ?assertEqual({?TYPE, 0}, new()). 148 | 149 | query_test() -> 150 | Boolean0 = new(), 151 | Boolean1 = {?TYPE, 1}, 152 | ?assertEqual(false, query(Boolean0)), 153 | ?assertEqual(true, query(Boolean1)). 154 | 155 | delta_true_test() -> 156 | Boolean0 = new(), 157 | {ok, {?TYPE, Delta1}} = delta_mutate(true, 1, Boolean0), 158 | ?assertEqual({?TYPE, 1}, {?TYPE, Delta1}). 159 | 160 | true_test() -> 161 | Boolean0 = new(), 162 | {ok, Boolean1} = mutate(true, 1, Boolean0), 163 | ?assertEqual({?TYPE, 1}, Boolean1). 164 | 165 | merge_test() -> 166 | Boolean1 = {?TYPE, 0}, 167 | Boolean2 = {?TYPE, 1}, 168 | Boolean3 = merge(Boolean1, Boolean1), 169 | Boolean4 = merge(Boolean1, Boolean2), 170 | Boolean5 = merge(Boolean2, Boolean1), 171 | Boolean6 = merge(Boolean2, Boolean2), 172 | ?assertEqual({?TYPE, 0}, Boolean3), 173 | ?assertEqual({?TYPE, 1}, Boolean4), 174 | ?assertEqual({?TYPE, 1}, Boolean5), 175 | ?assertEqual({?TYPE, 1}, Boolean6). 176 | 177 | merge_deltas_test() -> 178 | Boolean1 = {?TYPE, 0}, 179 | Delta1 = {?TYPE, 0}, 180 | Delta2 = {?TYPE, 1}, 181 | Boolean3 = merge(Delta1, Boolean1), 182 | Boolean4 = merge(Boolean1, Delta1), 183 | DeltaGroup = merge(Delta1, Delta2), 184 | ?assertEqual({?TYPE, 0}, Boolean3), 185 | ?assertEqual({?TYPE, 0}, Boolean4), 186 | ?assertEqual({?TYPE, 1}, DeltaGroup). 187 | 188 | equal_test() -> 189 | Boolean1 = {?TYPE, 0}, 190 | Boolean2 = {?TYPE, 1}, 191 | ?assert(equal(Boolean1, Boolean1)), 192 | ?assertNot(equal(Boolean1, Boolean2)). 193 | 194 | is_bottom_test() -> 195 | Boolean0 = new(), 196 | Boolean1 = {?TYPE, 1}, 197 | ?assert(is_bottom(Boolean0)), 198 | ?assertNot(is_bottom(Boolean1)). 199 | 200 | is_inflation_test() -> 201 | Boolean1 = {?TYPE, 0}, 202 | Boolean2 = {?TYPE, 1}, 203 | ?assert(is_inflation(Boolean1, Boolean1)), 204 | ?assert(is_inflation(Boolean1, Boolean2)), 205 | ?assertNot(is_inflation(Boolean2, Boolean1)), 206 | ?assert(is_inflation(Boolean2, Boolean2)), 207 | %% check inflation with merge 208 | ?assert(state_type:is_inflation(Boolean1, Boolean1)), 209 | ?assert(state_type:is_inflation(Boolean1, Boolean2)), 210 | ?assertNot(state_type:is_inflation(Boolean2, Boolean1)), 211 | ?assert(state_type:is_inflation(Boolean2, Boolean2)). 212 | 213 | is_strict_inflation_test() -> 214 | Boolean1 = {?TYPE, 0}, 215 | Boolean2 = {?TYPE, 1}, 216 | ?assertNot(is_strict_inflation(Boolean1, Boolean1)), 217 | ?assert(is_strict_inflation(Boolean1, Boolean2)), 218 | ?assertNot(is_strict_inflation(Boolean2, Boolean1)), 219 | ?assertNot(is_strict_inflation(Boolean2, Boolean2)). 220 | 221 | join_decomposition_test() -> 222 | Boolean1 = {?TYPE, 1}, 223 | Decomp1 = join_decomposition(Boolean1), 224 | ?assertEqual([Boolean1], Decomp1). 225 | 226 | encode_decode_test() -> 227 | Boolean = {?TYPE, 1}, 228 | Binary = encode(erlang, Boolean), 229 | EBoolean = decode(erlang, Binary), 230 | ?assertEqual(Boolean, EBoolean). 231 | -endif. 232 | -------------------------------------------------------------------------------- /src/state_causal_type.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Common library for causal CRDTs. 21 | %% 22 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 23 | %% Delta State Replicated Data Types (2016) 24 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 25 | 26 | -module(state_causal_type). 27 | -author("Junghun Yoo "). 28 | 29 | -include("state_type.hrl"). 30 | 31 | -ifdef(TEST). 32 | -include_lib("eunit/include/eunit.hrl"). 33 | -endif. 34 | 35 | -export([new/1, 36 | merge/3, 37 | ds_bottom/1, 38 | is_element/3, 39 | dots/2]). 40 | 41 | -export_type([causal_crdt/0]). 42 | 43 | -type causal_crdt() :: { 44 | dot_store:dot_store(), 45 | causal_context:causal_context() 46 | }. 47 | 48 | %% @doc Create an empty CausalCRDT. 49 | -spec new(dot_set | dot_fun | dot_map) -> causal_crdt(). 50 | new(DotStoreType) -> 51 | {DotStoreType:new(), causal_context:new()}. 52 | 53 | %% @doc Universal merge for a two CausalCRDTs. 54 | -spec merge(dot_store:type(), causal_crdt(), causal_crdt()) -> 55 | causal_crdt(). 56 | merge(_Type, {DotStore, CausalContext}, {DotStore, CausalContext}) -> 57 | {DotStore, CausalContext}; 58 | merge(Type, A, B) -> 59 | merge(Type, A, B, true). 60 | 61 | merge(dot_set, {DotSetA, CausalContextA}, {DotSetB, CausalContextB}, MergeCausalContext) -> 62 | 63 | Intersection = dot_set:intersection(DotSetA, DotSetB), 64 | Subtract1 = dot_set:subtract_causal_context(DotSetA, 65 | CausalContextB), 66 | Subtract2 = dot_set:subtract_causal_context(DotSetB, 67 | CausalContextA), 68 | 69 | DotStore = dot_set:union(Intersection, 70 | dot_set:union(Subtract1, Subtract2)), 71 | 72 | CausalContext = case MergeCausalContext of 73 | true -> 74 | causal_context:union(CausalContextA, 75 | CausalContextB); 76 | false -> 77 | undefined 78 | end, 79 | 80 | {DotStore, CausalContext}; 81 | 82 | 83 | merge({dot_fun, CRDTType}=DSType, {DotFunA, CausalContextA}, 84 | {DotFunB, CausalContextB}, MergeCausalContext) -> 85 | 86 | Filtered1 = dot_fun:filter( 87 | fun(Dot, _) -> 88 | not causal_context:is_element(Dot, CausalContextB) 89 | end, 90 | DotFunA 91 | ), 92 | 93 | Filtered2 = dot_fun:filter( 94 | fun(Dot, _) -> 95 | not causal_context:is_element(Dot, CausalContextA) 96 | end, 97 | DotFunB 98 | ), 99 | 100 | DotSetA = dots(DSType, DotFunA), 101 | DotSetB = dots(DSType, DotFunB), 102 | Intersection = dot_set:intersection(DotSetA, DotSetB), 103 | 104 | DotStore0 = dot_set:fold( 105 | fun(Dot, DotFun) -> 106 | ValueA = dot_fun:fetch(Dot, DotFunA), 107 | ValueB = dot_fun:fetch(Dot, DotFunB), 108 | {CRDTType, Value} = CRDTType:merge({CRDTType, ValueA}, 109 | {CRDTType, ValueB}), 110 | dot_fun:store(Dot, Value, DotFun) 111 | end, 112 | dot_fun:new(), 113 | Intersection 114 | ), 115 | 116 | DotStore1 = lists:foldl( 117 | fun({Dot, Value}, DotFun) -> 118 | dot_fun:store(Dot, Value, DotFun) 119 | end, 120 | DotStore0, 121 | dot_fun:to_list(Filtered1) ++ dot_fun:to_list(Filtered2) 122 | ), 123 | 124 | CausalContext = case MergeCausalContext of 125 | true -> 126 | causal_context:union(CausalContextA, 127 | CausalContextB); 128 | false -> 129 | undefined 130 | end, 131 | 132 | {DotStore1, CausalContext}; 133 | 134 | merge({dot_map, Type}, {DotMapA, CausalContextA}, 135 | {DotMapB, CausalContextB}, MergeCausalContext) -> 136 | 137 | Default = ds_bottom(Type), 138 | DotStoreType = get_type(Type), 139 | 140 | %% merge two dot maps 141 | DotStore = dot_map:merge( 142 | fun(KeyDotStoreA, KeyDotStoreB) -> 143 | {VK, _} = merge(DotStoreType, 144 | {KeyDotStoreA, CausalContextA}, 145 | {KeyDotStoreB, CausalContextB}, 146 | false %% don't merge the CausalContext 147 | ), 148 | VK 149 | end, 150 | Default, 151 | DotMapA, 152 | DotMapB 153 | ), 154 | 155 | CausalContext = case MergeCausalContext of 156 | true -> 157 | causal_context:union(CausalContextA, 158 | CausalContextB); 159 | false -> 160 | undefined 161 | end, 162 | 163 | {DotStore, CausalContext}. 164 | 165 | %% @doc Get an empty DotStore. 166 | -spec ds_bottom(dot_store:type()) -> dot_store:dot_store(). 167 | ds_bottom(T) -> 168 | DSType = case get_type(T) of 169 | dot_set -> 170 | dot_set; 171 | {DS, _} -> 172 | DS 173 | end, 174 | DSType:new(). 175 | 176 | %% @doc Check if a dot belongs to the DotStore. 177 | -spec is_element(dot_store:type(), dot_store:dot(), 178 | dot_store:dot_store()) -> boolean(). 179 | is_element(dot_set, Dot, DotSet) -> 180 | dot_set:is_element(Dot, DotSet); 181 | is_element({dot_fun, _}, Dot, DotFun) -> 182 | dot_fun:is_element(Dot, DotFun); 183 | is_element({dot_map, T}, Dot, DotMap) -> 184 | dot_map:any( 185 | fun({_Key, DotStore}) -> 186 | is_element(get_type(T), Dot, DotStore) 187 | end, 188 | DotMap 189 | ). 190 | 191 | %% @doc Get dots from a DotStore. 192 | -spec dots(dot_store:type(), dot_store:dot_store()) -> 193 | dot_store:dot_set(). 194 | dots(dot_set, DotSet) -> 195 | DotSet; 196 | dots({dot_fun, _}, DotFun) -> 197 | Dots = [Dot || {Dot, _} <- dot_fun:to_list(DotFun)], 198 | dot_set:from_dots(Dots); 199 | dots({dot_map, T}, DotMap) -> 200 | dot_map:fold( 201 | fun(_, DS, Acc) -> 202 | DotSet = dots(T, DS), 203 | dot_set:union(DotSet, Acc) 204 | end, 205 | dot_set:new(), 206 | DotMap 207 | ); 208 | dots(Type, DS) -> 209 | dots(get_type(Type), DS). 210 | 211 | %% @private 212 | get_type(dot_set) -> 213 | dot_set; 214 | get_type({dot_fun, T}) -> 215 | {dot_fun, T}; 216 | get_type({dot_map, T}) -> 217 | {dot_map, get_type(T)}; 218 | get_type(?AWSET_TYPE) -> 219 | {dot_map, dot_set}; 220 | get_type(?DWFLAG_TYPE) -> 221 | dot_set; 222 | get_type(?EWFLAG_TYPE) -> 223 | dot_set; 224 | get_type(?MVREGISTER_TYPE) -> 225 | {dot_fun, ?IVAR_TYPE}; 226 | get_type({?AWMAP_TYPE, [T]}) -> 227 | {dot_map, get_type(T)}. 228 | -------------------------------------------------------------------------------- /src/state_gset.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% Copyright (c) 2007-2012 Basho Technologies, Inc. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | 21 | %% @doc GSet CRDT: grow only set. 22 | %% 23 | %% @reference Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero 24 | %% Delta State Replicated Data Types (2016) 25 | %% [http://arxiv.org/pdf/1603.01529v1.pdf] 26 | %% 27 | %% @reference Carlos Baquero 28 | %% delta-enabled-crdts C++ library 29 | %% [https://github.com/CBaquero/delta-enabled-crdts] 30 | 31 | -module(state_gset). 32 | -author("Vitor Enes Duarte "). 33 | 34 | -behaviour(type). 35 | -behaviour(state_type). 36 | 37 | -define(TYPE, ?MODULE). 38 | 39 | -ifdef(TEST). 40 | -include_lib("eunit/include/eunit.hrl"). 41 | -endif. 42 | 43 | -export([new/0, new/1]). 44 | -export([mutate/3, delta_mutate/3, merge/2]). 45 | -export([query/1, equal/2, is_bottom/1, 46 | is_inflation/2, is_strict_inflation/2, 47 | irreducible_is_strict_inflation/2]). 48 | -export([join_decomposition/1, delta/2, digest/1]). 49 | -export([encode/2, decode/2]). 50 | 51 | -export_type([state_gset/0, state_gset_op/0]). 52 | 53 | -opaque state_gset() :: {?TYPE, payload()}. 54 | -type payload() :: ordsets:ordset(any()). 55 | -type element() :: term(). 56 | -type state_gset_op() :: {add, element()}. 57 | 58 | %% @doc Create a new, empty `state_gset()' 59 | -spec new() -> state_gset(). 60 | new() -> 61 | {?TYPE, ordsets:new()}. 62 | 63 | %% @doc Create a new, empty `state_gset()' 64 | -spec new([term()]) -> state_gset(). 65 | new([]) -> 66 | new(). 67 | 68 | %% @doc Mutate a `state_gset()'. 69 | -spec mutate(state_gset_op(), type:id(), state_gset()) -> 70 | {ok, state_gset()}. 71 | mutate(Op, Actor, {?TYPE, _GSet}=CRDT) -> 72 | state_type:mutate(Op, Actor, CRDT). 73 | 74 | %% @doc Delta-mutate a `state_gset()'. 75 | %% The first argument can only be `{add, element()}'. 76 | %% The second argument is the replica id (unused). 77 | %% The third argument is the `state_gset()' to be inflated. 78 | %% Returns a `state_gset()' delta which is a new `state_gset()' 79 | %% with only one element - the element to be added to 80 | %% the set. If the element is already in the set 81 | %% the resulting delta will be an empty `state_gset()'. 82 | -spec delta_mutate(state_gset_op(), type:id(), state_gset()) -> 83 | {ok, state_gset()}. 84 | delta_mutate({add, Elem}, _Actor, {?TYPE, GSet}) -> 85 | Delta = case ordsets:is_element(Elem, GSet) of 86 | true -> 87 | ordsets:new(); 88 | false -> 89 | ordsets:add_element(Elem, ordsets:new()) 90 | end, 91 | {ok, {?TYPE, Delta}}. 92 | 93 | %% @doc Returns the value of the `state_gset()'. 94 | %% This value is a set with all the elements in the `state_gset()'. 95 | -spec query(state_gset()) -> sets:set(element()). 96 | query({?TYPE, GSet}) -> 97 | sets:from_list(GSet). 98 | 99 | %% @doc Merge two `state_gset()'. 100 | %% The result is the set union of both sets in the 101 | %% `state_gset()' passed as argument. 102 | -spec merge(state_gset(), state_gset()) -> state_gset(). 103 | merge({?TYPE, GSet1}, {?TYPE, GSet2}) -> 104 | GSet = ordsets:union(GSet1, GSet2), 105 | {?TYPE, GSet}. 106 | 107 | %% @doc Equality for `state_gset()'. 108 | -spec equal(state_gset(), state_gset()) -> boolean(). 109 | equal({?TYPE, GSet1}, {?TYPE, GSet2}) -> 110 | ordsets_ext:equal(GSet1, GSet2). 111 | 112 | %% @doc Check if a GSet is bottom. 113 | -spec is_bottom(state_gset()) -> boolean(). 114 | is_bottom({?TYPE, GSet}) -> 115 | ordsets:size(GSet) == 0. 116 | 117 | %% @doc Given two `state_gset()', check if the second is an inflation 118 | %% of the first. 119 | %% The second `state_gset()' is an inflation if the first set is 120 | %% a subset of the second. 121 | -spec is_inflation(state_gset(), state_gset()) -> boolean(). 122 | is_inflation({?TYPE, GSet1}, {?TYPE, GSet2}) -> 123 | ordsets:is_subset(GSet1, GSet2); 124 | 125 | %% @todo get back here later 126 | is_inflation({cardinality, Value1}, {?TYPE, _}=GSet) -> 127 | Value2 = query(GSet), 128 | sets:size(Value2) >= Value1. 129 | 130 | %% @doc Check for strict inflation. 131 | -spec is_strict_inflation(state_gset(), state_gset()) -> boolean(). 132 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 133 | state_type:is_strict_inflation(CRDT1, CRDT2); 134 | 135 | %% @todo get back here later 136 | is_strict_inflation({cardinality, Value1}, {?TYPE, _}=GSet) -> 137 | Value2 = query(GSet), 138 | sets:size(Value2) > Value1. 139 | 140 | %% @doc Check for irreducible strict inflation. 141 | -spec irreducible_is_strict_inflation(state_gset(), 142 | state_type:digest()) -> 143 | boolean(). 144 | irreducible_is_strict_inflation({?TYPE, [E]}, 145 | {state, {?TYPE, GSet}}) -> 146 | not ordsets:is_element(E, GSet). 147 | 148 | -spec digest(state_gset()) -> state_type:digest(). 149 | digest({?TYPE, _}=CRDT) -> 150 | {state, CRDT}. 151 | 152 | %% @doc Join decomposition for `state_gset()'. 153 | %% The join decompostion for a `state_gset()' is the unique set 154 | %% partition where each set of the partition has exactly one 155 | %% element. 156 | -spec join_decomposition(state_gset()) -> [state_gset()]. 157 | join_decomposition({?TYPE, GSet}) -> 158 | ordsets:fold( 159 | fun(Elem, Acc) -> 160 | [{?TYPE, [Elem]} | Acc] 161 | end, 162 | [], 163 | GSet 164 | ). 165 | 166 | %% @doc Delta calculation for `state_gset()'. 167 | -spec delta(state_gset(), state_type:digest()) -> state_gset(). 168 | delta({?TYPE, _}=A, B) -> 169 | state_type:delta(A, B). 170 | 171 | -spec encode(state_type:format(), state_gset()) -> binary(). 172 | encode(erlang, {?TYPE, _}=CRDT) -> 173 | erlang:term_to_binary(CRDT). 174 | 175 | -spec decode(state_type:format(), binary()) -> state_gset(). 176 | decode(erlang, Binary) -> 177 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 178 | CRDT. 179 | 180 | 181 | %% =================================================================== 182 | %% EUnit tests 183 | %% =================================================================== 184 | -ifdef(TEST). 185 | 186 | new_test() -> 187 | ?assertEqual({?TYPE, ordsets:new()}, new()). 188 | 189 | query_test() -> 190 | Set0 = new(), 191 | Set1 = {?TYPE, [<<"a">>]}, 192 | ?assertEqual(sets:new(), query(Set0)), 193 | ?assertEqual(sets:from_list([<<"a">>]), query(Set1)). 194 | 195 | delta_add_test() -> 196 | Actor = 1, 197 | Set0 = new(), 198 | {ok, {?TYPE, Delta1}} = delta_mutate({add, <<"a">>}, Actor, Set0), 199 | Set1 = merge({?TYPE, Delta1}, Set0), 200 | {ok, {?TYPE, Delta2}} = delta_mutate({add, <<"a">>}, Actor, Set1), 201 | Set2 = merge({?TYPE, Delta2}, Set1), 202 | {ok, {?TYPE, Delta3}} = delta_mutate({add, <<"b">>}, Actor, Set2), 203 | Set3 = merge({?TYPE, Delta3}, Set2), 204 | ?assertEqual({?TYPE, [<<"a">>]}, {?TYPE, Delta1}), 205 | ?assertEqual({?TYPE, [<<"a">>]}, Set1), 206 | ?assertEqual({?TYPE, []}, {?TYPE, Delta2}), 207 | ?assertEqual({?TYPE, [<<"a">>]}, Set2), 208 | ?assertEqual({?TYPE, [<<"b">>]}, {?TYPE, Delta3}), 209 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set3). 210 | 211 | add_test() -> 212 | Actor = 1, 213 | Set0 = new(), 214 | {ok, Set1} = mutate({add, <<"a">>}, Actor, Set0), 215 | {ok, Set2} = mutate({add, <<"b">>}, Actor, Set1), 216 | ?assertEqual({?TYPE, [<<"a">>]}, Set1), 217 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set2). 218 | 219 | merge_idempotent_test() -> 220 | Set1 = {?TYPE, [<<"a">>]}, 221 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 222 | Set3 = merge(Set1, Set1), 223 | Set4 = merge(Set2, Set2), 224 | ?assertEqual(Set1, Set3), 225 | ?assertEqual(Set2, Set4). 226 | 227 | merge_commutative_test() -> 228 | Set1 = {?TYPE, [<<"a">>]}, 229 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 230 | Set3 = merge(Set1, Set2), 231 | Set4 = merge(Set2, Set1), 232 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set3), 233 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set4). 234 | 235 | merge_deltas_test() -> 236 | Set1 = {?TYPE, [<<"a">>]}, 237 | Delta1 = {?TYPE, [<<"a">>, <<"b">>]}, 238 | Delta2 = {?TYPE, [<<"c">>]}, 239 | Set2 = merge(Delta1, Set1), 240 | Set3 = merge(Set1, Delta1), 241 | DeltaGroup = merge(Delta1, Delta2), 242 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set2), 243 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>]}, Set3), 244 | ?assertEqual({?TYPE, [<<"a">>, <<"b">>, <<"c">>]}, DeltaGroup). 245 | 246 | equal_test() -> 247 | Set1 = {?TYPE, [<<"a">>]}, 248 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 249 | ?assert(equal(Set1, Set1)), 250 | ?assertNot(equal(Set1, Set2)). 251 | 252 | is_bottom_test() -> 253 | Set0 = new(), 254 | Set1 = {?TYPE, [<<"a">>]}, 255 | ?assert(is_bottom(Set0)), 256 | ?assertNot(is_bottom(Set1)). 257 | 258 | is_inflation_test() -> 259 | Set1 = {?TYPE, [<<"a">>]}, 260 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 261 | ?assert(is_inflation(Set1, Set1)), 262 | ?assert(is_inflation(Set1, Set2)), 263 | ?assertNot(is_inflation(Set2, Set1)), 264 | %% check inflation with merge 265 | ?assert(state_type:is_inflation(Set1, Set1)), 266 | ?assert(state_type:is_inflation(Set1, Set2)), 267 | ?assertNot(state_type:is_inflation(Set2, Set1)). 268 | 269 | is_strict_inflation_test() -> 270 | Set1 = {?TYPE, [<<"a">>]}, 271 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 272 | ?assertNot(is_strict_inflation(Set1, Set1)), 273 | ?assert(is_strict_inflation(Set1, Set2)), 274 | ?assertNot(is_strict_inflation(Set2, Set1)). 275 | 276 | join_decomposition_test() -> 277 | Set1 = {?TYPE, [<<"a">>]}, 278 | Set2 = {?TYPE, [<<"a">>, <<"b">>]}, 279 | Decomp1 = join_decomposition(Set1), 280 | Decomp2 = join_decomposition(Set2), 281 | ?assertEqual([{?TYPE, [<<"a">>]}], Decomp1), 282 | ?assertEqual(lists:sort([{?TYPE, [<<"a">>]}, {?TYPE, [<<"b">>]}]), lists:sort(Decomp2)). 283 | 284 | delta_test() -> 285 | A = {?TYPE, ["a", "b", "c"]}, 286 | B = {state, {?TYPE, ["b", "d"]}}, 287 | Delta = delta(A, B), 288 | ?assertEqual({?TYPE, ["a", "c"]}, Delta). 289 | 290 | encode_decode_test() -> 291 | Set = {?TYPE, [<<"a">>, <<"b">>]}, 292 | Binary = encode(erlang, Set), 293 | ESet = decode(erlang, Binary), 294 | ?assertEqual(Set, ESet). 295 | 296 | -endif. 297 | -------------------------------------------------------------------------------- /src/state_ivar.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Single-assignment variable. 21 | %% Write once register. 22 | 23 | -module(state_ivar). 24 | -author("Vitor Enes Duarte "). 25 | 26 | -behaviour(type). 27 | -behaviour(state_type). 28 | 29 | -define(TYPE, ?MODULE). 30 | 31 | -ifdef(TEST). 32 | -include_lib("eunit/include/eunit.hrl"). 33 | -endif. 34 | 35 | -export([new/0, new/1]). 36 | -export([mutate/3, delta_mutate/3, merge/2]). 37 | -export([query/1, equal/2, is_bottom/1, 38 | is_inflation/2, is_strict_inflation/2, 39 | irreducible_is_strict_inflation/2]). 40 | -export([join_decomposition/1, delta/2, digest/1]). 41 | -export([encode/2, decode/2]). 42 | 43 | -export_type([state_ivar/0, state_ivar_op/0]). 44 | 45 | -opaque state_ivar() :: {?TYPE, payload()}. 46 | -type payload() :: term(). 47 | -type state_ivar_op() :: {set, term()}. 48 | 49 | %% @doc Create a new `state_ivar()' 50 | -spec new() -> state_ivar(). 51 | new() -> 52 | {?TYPE, undefined}. 53 | 54 | %% @doc Create a new, empty `state_ivar()' 55 | -spec new([term()]) -> state_ivar(). 56 | new([]) -> 57 | new(). 58 | 59 | %% @doc Mutate a `state_ivar()'. 60 | -spec mutate(state_ivar_op(), type:id(), state_ivar()) -> 61 | {ok, state_ivar()}. 62 | mutate({set, Value}, _Actor, {?TYPE, undefined}) -> 63 | {ok, {?TYPE, Value}}. 64 | 65 | %% @doc Delta-mutate a `state_ivar()'. 66 | -spec delta_mutate(state_ivar_op(), type:id(), state_ivar()) -> 67 | {ok, state_ivar()}. 68 | delta_mutate(Op, Actor, {?TYPE, _}=Var) -> 69 | {ok, {?TYPE, Value}} = mutate(Op, Actor, Var), 70 | {ok, {?TYPE, Value}}. 71 | 72 | %% @doc Returns the value of the `state_ivar()'. 73 | -spec query(state_ivar()) -> term(). 74 | query({?TYPE, Value}) -> 75 | Value. 76 | 77 | %% @doc Merge two `state_ivar()'. 78 | -spec merge(state_ivar(), state_ivar()) -> state_ivar(). 79 | merge({?TYPE, undefined}, {?TYPE, undefined}) -> 80 | {?TYPE, undefined}; 81 | merge({?TYPE, Value}, {?TYPE, undefined}) -> 82 | {?TYPE, Value}; 83 | merge({?TYPE, undefined}, {?TYPE, Value}) -> 84 | {?TYPE, Value}; 85 | merge({?TYPE, Value}, {?TYPE, Value}) -> 86 | {?TYPE, Value}. 87 | 88 | %% @doc Equality for `state_ivar()'. 89 | -spec equal(state_ivar(), state_ivar()) -> boolean(). 90 | equal({?TYPE, Value1}, {?TYPE, Value2}) -> 91 | Value1 == Value2. 92 | 93 | %% @doc Check if an IVar is bottom. 94 | -spec is_bottom(state_ivar()) -> boolean(). 95 | is_bottom({?TYPE, Value}) -> 96 | Value == undefined. 97 | 98 | %% @doc Given two `state_ivar()', check if the second is and inflation 99 | %% of the first. 100 | %% The second `state_ivar()' is and inflation if the first set is 101 | %% a subset of the second. 102 | -spec is_inflation(state_ivar(), state_ivar()) -> boolean(). 103 | is_inflation({?TYPE, undefined}, {?TYPE, undefined}) -> 104 | true; 105 | is_inflation({?TYPE, undefined}, {?TYPE, _Value}) -> 106 | true; 107 | is_inflation({?TYPE, _Value}, {?TYPE, undefined}) -> 108 | false; 109 | is_inflation({?TYPE, _}=Var1, {?TYPE, _}=Var2) -> 110 | equal(Var1, Var2). 111 | 112 | %% @doc Check for strict inflation. 113 | -spec is_strict_inflation(state_ivar(), state_ivar()) -> boolean(). 114 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 115 | state_type:is_strict_inflation(CRDT1, CRDT2). 116 | 117 | %% @doc Check for irreducible strict inflation. 118 | -spec irreducible_is_strict_inflation(state_ivar(), 119 | state_type:digest()) -> 120 | boolean(). 121 | irreducible_is_strict_inflation({?TYPE, _}=A, B) -> 122 | state_type:irreducible_is_strict_inflation(A, B). 123 | 124 | -spec digest(state_ivar()) -> state_type:digest(). 125 | digest({?TYPE, _}=CRDT) -> 126 | {state, CRDT}. 127 | 128 | %% @doc Join decomposition for `state_ivar()'. 129 | -spec join_decomposition(state_ivar()) -> [state_ivar()]. 130 | join_decomposition({?TYPE, _}=Var) -> 131 | [Var]. 132 | 133 | %% @doc Delta calculation for `state_ivar()'. 134 | -spec delta(state_ivar(), state_type:digest()) -> state_ivar(). 135 | delta({?TYPE, _}=A, B) -> 136 | state_type:delta(A, B). 137 | 138 | -spec encode(state_type:format(), state_ivar()) -> binary(). 139 | encode(erlang, {?TYPE, _}=CRDT) -> 140 | erlang:term_to_binary(CRDT). 141 | 142 | -spec decode(state_type:format(), binary()) -> state_ivar(). 143 | decode(erlang, Binary) -> 144 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 145 | CRDT. 146 | 147 | 148 | %% =================================================================== 149 | %% EUnit tests 150 | %% =================================================================== 151 | -ifdef(TEST). 152 | 153 | new_test() -> 154 | ?assertEqual({?TYPE, undefined}, new()). 155 | 156 | query_test() -> 157 | Var0 = new(), 158 | Var1 = {?TYPE, <<"a">>}, 159 | ?assertEqual(undefined, query(Var0)), 160 | ?assertEqual(<<"a">>, query(Var1)). 161 | 162 | set_test() -> 163 | Actor = 1, 164 | Var0 = new(), 165 | {ok, Var1} = mutate({set, <<"a">>}, Actor, Var0), 166 | ?assertEqual({?TYPE, <<"a">>}, Var1). 167 | 168 | merge_test() -> 169 | Var0 = new(), 170 | Var1 = {?TYPE, <<"a">>}, 171 | Var2 = merge(Var0, Var0), 172 | Var3 = merge(Var0, Var1), 173 | Var4 = merge(Var1, Var0), 174 | Var5 = merge(Var1, Var1), 175 | ?assertEqual({?TYPE, undefined}, Var2), 176 | ?assertEqual({?TYPE, <<"a">>}, Var3), 177 | ?assertEqual({?TYPE, <<"a">>}, Var4), 178 | ?assertEqual({?TYPE, <<"a">>}, Var5). 179 | 180 | equal_test() -> 181 | Var0 = new(), 182 | Var1 = {?TYPE, [<<"a">>]}, 183 | Var2 = {?TYPE, [<<"b">>]}, 184 | ?assert(equal(Var0, Var0)), 185 | ?assertNot(equal(Var0, Var1)), 186 | ?assert(equal(Var1, Var1)), 187 | ?assertNot(equal(Var1, Var2)). 188 | 189 | is_bottom_test() -> 190 | Var0 = new(), 191 | Var1 = {?TYPE, [<<"a">>]}, 192 | ?assert(is_bottom(Var0)), 193 | ?assertNot(is_bottom(Var1)). 194 | 195 | is_inflation_test() -> 196 | Var0 = new(), 197 | Var1 = {?TYPE, [<<"a">>]}, 198 | Var2 = {?TYPE, [<<"b">>]}, 199 | ?assert(is_inflation(Var0, Var0)), 200 | ?assert(is_inflation(Var0, Var1)), 201 | ?assert(is_inflation(Var1, Var1)), 202 | ?assertNot(is_inflation(Var1, Var2)), 203 | ?assertNot(is_inflation(Var2, Var1)), 204 | %% check inflation with merge 205 | ?assert(state_type:is_inflation(Var0, Var0)), 206 | ?assert(state_type:is_inflation(Var0, Var1)), 207 | ?assert(state_type:is_inflation(Var1, Var1)). 208 | 209 | is_strict_inflation_test() -> 210 | Var0 = new(), 211 | Var1 = {?TYPE, [<<"a">>]}, 212 | Var2 = {?TYPE, [<<"b">>]}, 213 | ?assertNot(is_strict_inflation(Var0, Var0)), 214 | ?assert(is_strict_inflation(Var0, Var1)), 215 | ?assertNot(is_strict_inflation(Var1, Var1)), 216 | ?assertNot(is_strict_inflation(Var1, Var2)), 217 | ?assertNot(is_strict_inflation(Var2, Var1)). 218 | 219 | join_decomposition_test() -> 220 | Var0 = new(), 221 | Var1 = {?TYPE, <<"a">>}, 222 | Decomp0 = join_decomposition(Var0), 223 | Decomp1 = join_decomposition(Var1), 224 | ?assertEqual([{?TYPE, undefined}], Decomp0), 225 | ?assertEqual([{?TYPE, <<"a">>}], Decomp1). 226 | 227 | encode_decode_test() -> 228 | Var = {?TYPE, [<<"a">>]}, 229 | Binary = encode(erlang, Var), 230 | EVar = decode(erlang, Binary), 231 | ?assertEqual(Var, EVar). 232 | 233 | -endif. 234 | -------------------------------------------------------------------------------- /src/state_lwwregister.erl: -------------------------------------------------------------------------------- 1 | % % 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc LWWRegister. 21 | %% We assume timestamp are unique, totally ordered and consistent 22 | %% with causal order. We use integers as timestamps. 23 | %% When using this, make sure you provide globally unique 24 | %% timestamps. 25 | %% 26 | %% @reference Marc Shapiro, Nuno Preguiça, Carlos Baquero and Marek Zawirsk 27 | %% A comprehensive study of Convergent and Commutative Replicated Data Types (2011) 28 | %% [http://hal.upmc.fr/file/index/docid/555588/filename/techreport.pdf] 29 | %% 30 | %% @reference Carlos Baquero 31 | %% delta-enabled-crdts C++ library 32 | %% [https://github.com/CBaquero/delta-enabled-crdts] 33 | 34 | -module(state_lwwregister). 35 | -author("Vitor Enes Duarte "). 36 | 37 | -behaviour(type). 38 | -behaviour(state_type). 39 | 40 | -define(TYPE, ?MODULE). 41 | 42 | -ifdef(TEST). 43 | -include_lib("eunit/include/eunit.hrl"). 44 | -endif. 45 | 46 | -export([new/0, new/1]). 47 | -export([mutate/3, delta_mutate/3, merge/2]). 48 | -export([query/1, equal/2, is_bottom/1, 49 | is_inflation/2, is_strict_inflation/2, 50 | irreducible_is_strict_inflation/2]). 51 | -export([join_decomposition/1, delta/2, digest/1]). 52 | -export([encode/2, decode/2]). 53 | 54 | -export_type([state_lwwregister/0, state_lwwregister_op/0]). 55 | 56 | -opaque state_lwwregister() :: {?TYPE, payload()}. 57 | -type payload() :: {timestamp(), value()}. 58 | -type timestamp() :: non_neg_integer(). 59 | -type value() :: term(). 60 | -type state_lwwregister_op() :: {set, timestamp(), value()}. 61 | 62 | %% @doc Create a new, empty `state_lwwregister()' 63 | -spec new() -> state_lwwregister(). 64 | new() -> 65 | {?TYPE, {0, undefined}}. 66 | 67 | %% @doc Create a new, empty `state_lwwregister()' 68 | -spec new([term()]) -> state_lwwregister(). 69 | new([]) -> 70 | new(). 71 | 72 | %% @doc Mutate a `state_lwwregister()'. 73 | -spec mutate(state_lwwregister_op(), type:id(), state_lwwregister()) -> 74 | {ok, state_lwwregister()}. 75 | mutate(Op, Actor, {?TYPE, _Register}=CRDT) -> 76 | state_type:mutate(Op, Actor, CRDT). 77 | 78 | %% @doc Delta-mutate a `state_lwwregister()'. 79 | %% The first argument can only be `{set, timestamp(), value()}'. 80 | %% The second argument is the replica id (unused). 81 | %% The third argument is the `state_lwwregister()' to be inflated. 82 | -spec delta_mutate(state_lwwregister_op(), type:id(), state_lwwregister()) -> 83 | {ok, state_lwwregister()}. 84 | delta_mutate({set, Timestamp, Value}, _Actor, {?TYPE, _Register}) when is_integer(Timestamp) -> 85 | {ok, {?TYPE, {Timestamp, Value}}}. 86 | 87 | %% @doc Returns the value of the `state_lwwregister()'. 88 | -spec query(state_lwwregister()) -> value(). 89 | query({?TYPE, {_Timestamp, Value}}) -> 90 | Value. 91 | 92 | %% @doc Merge two `state_lwwregister()'. 93 | %% The result is the set union of both sets in the 94 | %% `state_lwwregister()' passed as argument. 95 | -spec merge(state_lwwregister(), state_lwwregister()) -> state_lwwregister(). 96 | merge({?TYPE, {Timestamp1, Value1}}, {?TYPE, {Timestamp2, Value2}}) -> 97 | Register = case Timestamp1 > Timestamp2 of 98 | true -> 99 | {Timestamp1, Value1}; 100 | false -> 101 | {Timestamp2, Value2} 102 | end, 103 | {?TYPE, Register}. 104 | 105 | %% @doc Equality for `state_lwwregister()'. 106 | -spec equal(state_lwwregister(), state_lwwregister()) -> boolean(). 107 | equal({?TYPE, Register1}, {?TYPE, Register2}) -> 108 | Register1 == Register2. 109 | 110 | %% @doc Check if a Register is bottom. 111 | -spec is_bottom(state_lwwregister()) -> boolean(). 112 | is_bottom({?TYPE, _}=CRDT) -> 113 | CRDT == new(). 114 | 115 | %% @doc Given two `state_lwwregister()', check if the second is an inflation 116 | %% of the first. 117 | %% The second `state_lwwregister()' it has a higher timestamp or the same 118 | %% timstamp (and in this case, they are equal). 119 | -spec is_inflation(state_lwwregister(), state_lwwregister()) -> boolean(). 120 | is_inflation({?TYPE, {Timestamp1, _}}, {?TYPE, {Timestamp2, _}}) -> 121 | Timestamp2 >= Timestamp1. 122 | 123 | %% @doc Check for strict inflation. 124 | -spec is_strict_inflation(state_lwwregister(), state_lwwregister()) -> boolean(). 125 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 126 | state_type:is_strict_inflation(CRDT1, CRDT2). 127 | 128 | %% @doc Check for irreducible strict inflation. 129 | -spec irreducible_is_strict_inflation(state_lwwregister(), 130 | state_type:digest()) -> 131 | boolean(). 132 | irreducible_is_strict_inflation({?TYPE, _}=A, B) -> 133 | state_type:irreducible_is_strict_inflation(A, B). 134 | 135 | -spec digest(state_lwwregister()) -> state_type:digest(). 136 | digest({?TYPE, _}=CRDT) -> 137 | {state, CRDT}. 138 | 139 | %% @doc Join decomposition for `state_lwwregister()'. 140 | -spec join_decomposition(state_lwwregister()) -> [state_lwwregister()]. 141 | join_decomposition({?TYPE, _}=CRDT) -> 142 | [CRDT]. 143 | 144 | %% @doc Delta calculation for `state_lwwregister()'. 145 | -spec delta(state_lwwregister(), state_type:digest()) -> state_lwwregister(). 146 | delta({?TYPE, _}=A, B) -> 147 | state_type:delta(A, B). 148 | 149 | -spec encode(state_type:format(), state_lwwregister()) -> binary(). 150 | encode(erlang, {?TYPE, _}=CRDT) -> 151 | erlang:term_to_binary(CRDT). 152 | 153 | -spec decode(state_type:format(), binary()) -> state_lwwregister(). 154 | decode(erlang, Binary) -> 155 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 156 | CRDT. 157 | 158 | 159 | %% =================================================================== 160 | %% EUnit tests 161 | %% =================================================================== 162 | -ifdef(TEST). 163 | 164 | new_test() -> 165 | Bottom = {0, undefined}, 166 | ?assertEqual({?TYPE, Bottom}, new()). 167 | 168 | query_test() -> 169 | Register0 = new(), 170 | Register1 = {?TYPE, {1234, "a"}}, 171 | ?assertEqual(undefined, query(Register0)), 172 | ?assertEqual("a", query(Register1)). 173 | 174 | delta_set_test() -> 175 | Actor = 1, 176 | Register0 = new(), 177 | {ok, {?TYPE, Delta1}} = delta_mutate({set, 1234, "a"}, Actor, Register0), 178 | Register1 = merge({?TYPE, Delta1}, Register0), 179 | {ok, {?TYPE, Delta2}} = delta_mutate({set, 1235, "b"}, Actor, Register1), 180 | Register2 = merge({?TYPE, Delta2}, Register1), 181 | ?assertEqual({?TYPE, {1234, "a"}}, {?TYPE, Delta1}), 182 | ?assertEqual({?TYPE, {1234, "a"}}, Register1), 183 | ?assertEqual({?TYPE, {1235, "b"}}, {?TYPE, Delta2}), 184 | ?assertEqual({?TYPE, {1235, "b"}}, Register2). 185 | 186 | set_test() -> 187 | Actor = 1, 188 | Register0 = new(), 189 | {ok, Register1} = mutate({set, 1234, "a"}, Actor, Register0), 190 | {ok, Register2} = mutate({set, 1235, "b"}, Actor, Register1), 191 | ?assertEqual({?TYPE, {1234, "a"}}, Register1), 192 | ?assertEqual({?TYPE, {1235, "b"}}, Register2). 193 | 194 | merge_idempotent_test() -> 195 | Register1 = {?TYPE, {1234, "a"}}, 196 | Register2 = {?TYPE, {1235, "b"}}, 197 | Register3 = merge(Register1, Register1), 198 | Register4 = merge(Register2, Register2), 199 | ?assertEqual(Register1, Register3), 200 | ?assertEqual(Register2, Register4). 201 | 202 | merge_commutative_test() -> 203 | Register1 = {?TYPE, {1234, "a"}}, 204 | Register2 = {?TYPE, {1235, "b"}}, 205 | Register3 = merge(Register1, Register2), 206 | Register4 = merge(Register2, Register1), 207 | ?assertEqual(Register2, Register3), 208 | ?assertEqual(Register2, Register4). 209 | 210 | equal_test() -> 211 | Register1 = {?TYPE, {1234, "a"}}, 212 | Register2 = {?TYPE, {1235, "b"}}, 213 | ?assert(equal(Register1, Register1)), 214 | ?assertNot(equal(Register1, Register2)). 215 | 216 | is_bottom_test() -> 217 | Register0 = new(), 218 | Register1 = {?TYPE, {1234, "a"}}, 219 | ?assert(is_bottom(Register0)), 220 | ?assertNot(is_bottom(Register1)). 221 | 222 | is_inflation_test() -> 223 | Register1 = {?TYPE, {1234, "a"}}, 224 | Register2 = {?TYPE, {1235, "b"}}, 225 | ?assert(is_inflation(Register1, Register1)), 226 | ?assert(is_inflation(Register1, Register2)), 227 | ?assertNot(is_inflation(Register2, Register1)), 228 | %% check inflation with merge 229 | ?assert(state_type:is_inflation(Register1, Register1)), 230 | ?assert(state_type:is_inflation(Register1, Register2)), 231 | ?assertNot(state_type:is_inflation(Register2, Register1)). 232 | 233 | is_strict_inflation_test() -> 234 | Register1 = {?TYPE, {1234, "a"}}, 235 | Register2 = {?TYPE, {1235, "b"}}, 236 | ?assertNot(is_strict_inflation(Register1, Register1)), 237 | ?assert(is_strict_inflation(Register1, Register2)), 238 | ?assertNot(is_strict_inflation(Register2, Register1)). 239 | 240 | join_decomposition_test() -> 241 | Register = {?TYPE, {1234, "a"}}, 242 | Decomp = join_decomposition(Register), 243 | ?assertEqual([Register], Decomp). 244 | 245 | encode_decode_test() -> 246 | Register = {?TYPE, {1234, "a"}}, 247 | Binary = encode(erlang, Register), 248 | ERegister = decode(erlang, Binary), 249 | ?assertEqual(Register, ERegister). 250 | 251 | -endif. 252 | -------------------------------------------------------------------------------- /src/state_max_int.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Max Int CRDT. 21 | %% 22 | %% @reference Carlos Baquero, Paulo Sérgio Almeida, Alcino Cunha and Carla Ferreira 23 | %% Composition of State-based CRDTs (2015) 24 | %% [http://haslab.uminho.pt/cbm/files/crdtcompositionreport.pdf] 25 | 26 | -module(state_max_int). 27 | -author("Vitor Enes Duarte "). 28 | 29 | -behaviour(type). 30 | -behaviour(state_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, delta_mutate/3, merge/2]). 40 | -export([query/1, equal/2, is_bottom/1, 41 | is_inflation/2, is_strict_inflation/2, 42 | irreducible_is_strict_inflation/2]). 43 | -export([join_decomposition/1, delta/2, digest/1]). 44 | -export([encode/2, decode/2]). 45 | 46 | -export_type([state_max_int/0, state_max_int_op/0]). 47 | 48 | -opaque state_max_int() :: {?TYPE, payload()}. 49 | -type payload() :: non_neg_integer(). 50 | -type state_max_int_op() :: increment. 51 | 52 | %% @doc Create a new `state_max_int()' 53 | -spec new() -> state_max_int(). 54 | new() -> 55 | {?TYPE, 0}. 56 | 57 | %% @doc Create a new `state_max_int()' 58 | -spec new([term()]) -> state_max_int(). 59 | new([]) -> 60 | new(). 61 | 62 | %% @doc Mutate a `state_max_int()'. 63 | -spec mutate(state_max_int_op(), type:id(), state_max_int()) -> 64 | {ok, state_max_int()}. 65 | mutate(Op, Actor, {?TYPE, _}=CRDT) -> 66 | state_type:mutate(Op, Actor, CRDT). 67 | 68 | %% @doc Delta-mutate a `state_max_int()'. 69 | %% The first argument can only be `increment'. 70 | %% Returns a `state_max_int()' delta which is a new `state_max_int()' 71 | %% with the value incremented by one. 72 | -spec delta_mutate(state_max_int_op(), type:id(), state_max_int()) -> 73 | {ok, state_max_int()}. 74 | delta_mutate(increment, _Actor, {?TYPE, Value}) -> 75 | {ok, {?TYPE, Value + 1}}. 76 | 77 | %% @doc Returns the value of the `state_max_int()'. 78 | -spec query(state_max_int()) -> non_neg_integer(). 79 | query({?TYPE, Value}) -> 80 | Value. 81 | 82 | %% @doc Merge two `state_max_int()'. 83 | %% Join is the max function. 84 | -spec merge(state_max_int(), state_max_int()) -> state_max_int(). 85 | merge({?TYPE, Value1}, {?TYPE, Value2}) -> 86 | {?TYPE, max(Value1, Value2)}. 87 | 88 | %% @doc Equality for `state_max_int()'. 89 | -spec equal(state_max_int(), state_max_int()) -> boolean(). 90 | equal({?TYPE, Value1}, {?TYPE, Value2}) -> 91 | Value1 == Value2. 92 | 93 | %% @doc Check if a Max Int is bottom. 94 | -spec is_bottom(state_max_int()) -> boolean(). 95 | is_bottom({?TYPE, Value}) -> 96 | Value == 0. 97 | 98 | %% @doc Given two `state_max_int()', check if the second is an inflation 99 | %% of the first. 100 | %% The second is an inflation if its value is greater or equal 101 | %% to the value of the first. 102 | -spec is_inflation(state_max_int(), state_max_int()) -> boolean(). 103 | is_inflation({?TYPE, Value1}, {?TYPE, Value2}) -> 104 | Value1 =< Value2. 105 | 106 | %% @doc Check for strict inflation. 107 | -spec is_strict_inflation(state_max_int(), state_max_int()) -> boolean(). 108 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 109 | state_type:is_strict_inflation(CRDT1, CRDT2). 110 | 111 | %% @doc Check for irreducible strict inflation. 112 | -spec irreducible_is_strict_inflation(state_max_int(), 113 | state_type:digest()) -> 114 | boolean(). 115 | irreducible_is_strict_inflation({?TYPE, _}=A, B) -> 116 | state_type:irreducible_is_strict_inflation(A, B). 117 | 118 | -spec digest(state_max_int()) -> state_type:digest(). 119 | digest({?TYPE, _}=CRDT) -> 120 | {state, CRDT}. 121 | 122 | %% @doc Join decomposition for `state_max_int()'. 123 | -spec join_decomposition(state_max_int()) -> [state_max_int()]. 124 | join_decomposition({?TYPE, _}=MaxInt) -> 125 | [MaxInt]. 126 | 127 | %% @doc Delta calculation for `state_max_int()'. 128 | -spec delta(state_max_int(), state_type:digest()) -> state_max_int(). 129 | delta({?TYPE, _}=A, B) -> 130 | state_type:delta(A, B). 131 | 132 | -spec encode(state_type:format(), state_max_int()) -> binary(). 133 | encode(erlang, {?TYPE, _}=CRDT) -> 134 | erlang:term_to_binary(CRDT). 135 | 136 | -spec decode(state_type:format(), binary()) -> state_max_int(). 137 | decode(erlang, Binary) -> 138 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 139 | CRDT. 140 | 141 | 142 | %% =================================================================== 143 | %% EUnit tests 144 | %% =================================================================== 145 | -ifdef(TEST). 146 | 147 | new_test() -> 148 | ?assertEqual({?TYPE, 0}, new()). 149 | 150 | query_test() -> 151 | MaxInt0 = new(), 152 | MaxInt1 = {?TYPE, 17}, 153 | ?assertEqual(0, query(MaxInt0)), 154 | ?assertEqual(17, query(MaxInt1)). 155 | 156 | delta_increment_test() -> 157 | Actor = 1, 158 | MaxInt0 = new(), 159 | {ok, {?TYPE, Delta1}} = delta_mutate(increment, Actor, MaxInt0), 160 | MaxInt1 = merge({?TYPE, Delta1}, MaxInt0), 161 | {ok, {?TYPE, Delta2}} = delta_mutate(increment, Actor, MaxInt1), 162 | MaxInt2 = merge({?TYPE, Delta2}, MaxInt1), 163 | ?assertEqual({?TYPE, 1}, {?TYPE, Delta1}), 164 | ?assertEqual({?TYPE, 1}, MaxInt1), 165 | ?assertEqual({?TYPE, 2}, {?TYPE, Delta2}), 166 | ?assertEqual({?TYPE, 2}, MaxInt2). 167 | 168 | increment_test() -> 169 | Actor = 1, 170 | MaxInt0 = {?TYPE, 15}, 171 | {ok, MaxInt1} = mutate(increment, Actor, MaxInt0), 172 | {ok, MaxInt2} = mutate(increment, Actor, MaxInt1), 173 | ?assertEqual({?TYPE, 16}, MaxInt1), 174 | ?assertEqual({?TYPE, 17}, MaxInt2). 175 | 176 | merge_idempotent_test() -> 177 | MaxInt1 = {?TYPE, 1}, 178 | MaxInt2 = {?TYPE, 17}, 179 | MaxInt3 = merge(MaxInt1, MaxInt1), 180 | MaxInt4 = merge(MaxInt2, MaxInt2), 181 | ?assertEqual(MaxInt1, MaxInt3), 182 | ?assertEqual(MaxInt2, MaxInt4). 183 | 184 | merge_commutative_test() -> 185 | MaxInt1 = {?TYPE, 1}, 186 | MaxInt2 = {?TYPE, 17}, 187 | MaxInt3 = merge(MaxInt1, MaxInt2), 188 | MaxInt4 = merge(MaxInt2, MaxInt1), 189 | ?assertEqual({?TYPE, 17}, MaxInt3), 190 | ?assertEqual({?TYPE, 17}, MaxInt4). 191 | 192 | merge_deltas_test() -> 193 | MaxInt1 = {?TYPE, 1}, 194 | Delta1 = {?TYPE, 17}, 195 | Delta2 = {?TYPE, 23}, 196 | MaxInt2 = merge(Delta1, MaxInt1), 197 | MaxInt3 = merge(MaxInt1, Delta1), 198 | DeltaGroup = merge(Delta1, Delta2), 199 | ?assertEqual({?TYPE, 17}, MaxInt2), 200 | ?assertEqual({?TYPE, 17}, MaxInt3), 201 | ?assertEqual({?TYPE, 23}, DeltaGroup). 202 | 203 | equal_test() -> 204 | MaxInt1 = {?TYPE, 17}, 205 | MaxInt2 = {?TYPE, 23}, 206 | ?assert(equal(MaxInt1, MaxInt1)), 207 | ?assertNot(equal(MaxInt1, MaxInt2)). 208 | 209 | is_bottom_test() -> 210 | MaxInt0 = new(), 211 | MaxInt1 = {?TYPE, 17}, 212 | ?assert(is_bottom(MaxInt0)), 213 | ?assertNot(is_bottom(MaxInt1)). 214 | 215 | is_inflation_test() -> 216 | MaxInt1 = {?TYPE, 23}, 217 | MaxInt2 = {?TYPE, 42}, 218 | ?assert(is_inflation(MaxInt1, MaxInt1)), 219 | ?assert(is_inflation(MaxInt1, MaxInt2)), 220 | ?assertNot(is_inflation(MaxInt2, MaxInt1)), 221 | %% check inflation with merge 222 | ?assert(state_type:is_inflation(MaxInt1, MaxInt1)), 223 | ?assert(state_type:is_inflation(MaxInt1, MaxInt2)), 224 | ?assertNot(state_type:is_inflation(MaxInt2, MaxInt1)). 225 | 226 | is_strict_inflation_test() -> 227 | MaxInt1 = {?TYPE, 23}, 228 | MaxInt2 = {?TYPE, 42}, 229 | ?assertNot(is_strict_inflation(MaxInt1, MaxInt1)), 230 | ?assert(is_strict_inflation(MaxInt1, MaxInt2)), 231 | ?assertNot(is_strict_inflation(MaxInt2, MaxInt1)). 232 | 233 | join_decomposition_test() -> 234 | MaxInt1 = {?TYPE, 17}, 235 | Decomp1 = join_decomposition(MaxInt1), 236 | ?assertEqual([{?TYPE, 17}], Decomp1). 237 | 238 | encode_decode_test() -> 239 | MaxInt = {?TYPE, 17}, 240 | Binary = encode(erlang, MaxInt), 241 | EMaxInt = decode(erlang, Binary), 242 | ?assertEqual(MaxInt, EMaxInt). 243 | 244 | -endif. 245 | -------------------------------------------------------------------------------- /src/state_mvmap.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Multi-Value Map CRDT. 21 | %% MVMap = AWMap> 22 | %% = DotMap> 23 | 24 | -module(state_mvmap). 25 | -author("Vitor Enes Duarte "). 26 | 27 | -include("state_type.hrl"). 28 | 29 | -behaviour(type). 30 | -behaviour(state_type). 31 | 32 | -define(TYPE, ?MODULE). 33 | 34 | -ifdef(TEST). 35 | -include_lib("eunit/include/eunit.hrl"). 36 | -endif. 37 | 38 | -export([new/0, new/1]). 39 | -export([mutate/3, delta_mutate/3, merge/2]). 40 | -export([query/1, equal/2, is_bottom/1, 41 | is_inflation/2, is_strict_inflation/2, 42 | irreducible_is_strict_inflation/2]). 43 | -export([join_decomposition/1, delta/2, digest/1]). 44 | -export([encode/2, decode/2]). 45 | 46 | -export_type([state_mvmap/0, state_mvmap_op/0]). 47 | 48 | -opaque state_mvmap() :: {?TYPE, payload()}. 49 | -type payload() :: state_awmap:state_awmap(). 50 | -type key() :: term(). 51 | -type value() :: term(). 52 | -type state_mvmap_op() :: {set, key(), value()}. 53 | 54 | %% @doc Create a new, empty `state_mvmap()'. 55 | -spec new() -> state_mvmap(). 56 | new() -> 57 | {?TYPE, ?AWMAP_TYPE:new([?MVREGISTER_TYPE])}. 58 | 59 | %% @doc Create a new, empty `state_mvmap()' 60 | -spec new([term()]) -> state_mvmap(). 61 | new([]) -> 62 | new(). 63 | 64 | %% @doc Mutate a `state_mvmap()'. 65 | -spec mutate(state_mvmap_op(), type:id(), state_mvmap()) -> 66 | {ok, state_mvmap()}. 67 | mutate(Op, Actor, {?TYPE, _}=CRDT) -> 68 | state_type:mutate(Op, Actor, CRDT). 69 | 70 | %% @doc Delta-mutate a `state_mvmap()'. 71 | %% The first argument can be: 72 | %% - `{set, Key, Value}' 73 | %% The second argument is the replica id. 74 | %% The third argument is the `state_mvmap()' to be inflated. 75 | -spec delta_mutate(state_mvmap_op(), type:id(), state_mvmap()) -> 76 | {ok, state_mvmap()}. 77 | delta_mutate({set, Key, Value}, Actor, {?TYPE, AWMap}) -> 78 | {ok, Delta} = ?AWMAP_TYPE:delta_mutate( 79 | {apply, Key, {set, undefined, Value}}, 80 | Actor, 81 | AWMap 82 | ), 83 | {ok, {?TYPE, Delta}}. 84 | 85 | %% @doc Returns the value of the `state_mvmap()'. 86 | %% This value is a dictionary where each key maps to the 87 | %% result of `query/1' over the current value. 88 | -spec query(state_mvmap()) -> term(). 89 | query({?TYPE, AWMap}) -> 90 | ?AWMAP_TYPE:query(AWMap). 91 | 92 | %% @doc Merge two `state_mvmap()'. 93 | -spec merge(state_mvmap(), state_mvmap()) -> state_mvmap(). 94 | merge({?TYPE, AWMap1}, {?TYPE, AWMap2}) -> 95 | Map = ?AWMAP_TYPE:merge(AWMap1, AWMap2), 96 | {?TYPE, Map}. 97 | 98 | %% @doc Equality for `state_mvmap()'. 99 | %% Since everything is ordered, == should work. 100 | -spec equal(state_mvmap(), state_mvmap()) -> boolean(). 101 | equal({?TYPE, AWMap1}, {?TYPE, AWMap2}) -> 102 | ?AWMAP_TYPE:equal(AWMap1, AWMap2). 103 | 104 | %% @doc Check if a `state_mvmap()' is bottom 105 | -spec is_bottom(state_mvmap()) -> boolean(). 106 | is_bottom({?TYPE, _}=CRDT) -> 107 | CRDT == new(). 108 | 109 | %% @doc Given two `state_mvmap()', check if the second is an inflation 110 | %% of the first. 111 | %% @todo 112 | -spec is_inflation(state_mvmap(), state_mvmap()) -> boolean(). 113 | is_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 114 | state_type:is_inflation(CRDT1, CRDT2). 115 | 116 | %% @doc Check for strict inflation. 117 | -spec is_strict_inflation(state_mvmap(), state_mvmap()) -> boolean(). 118 | is_strict_inflation({?TYPE, _}=CRDT1, {?TYPE, _}=CRDT2) -> 119 | state_type:is_strict_inflation(CRDT1, CRDT2). 120 | 121 | %% @doc Check for irreducible strict inflation. 122 | -spec irreducible_is_strict_inflation(state_mvmap(), 123 | state_type:digest()) -> 124 | boolean(). 125 | irreducible_is_strict_inflation({?TYPE, _}=A, B) -> 126 | state_type:irreducible_is_strict_inflation(A, B). 127 | 128 | -spec digest(state_mvmap()) -> state_type:digest(). 129 | digest({?TYPE, _}=CRDT) -> 130 | {state, CRDT}. 131 | 132 | %% @doc Join decomposition for `state_mvmap()'. 133 | %% @todo 134 | -spec join_decomposition(state_mvmap()) -> [state_mvmap()]. 135 | join_decomposition({?TYPE, _}=CRDT) -> 136 | [CRDT]. 137 | 138 | %% @doc Delta calculation for `state_mvmap()'. 139 | -spec delta(state_mvmap(), state_type:digest()) -> state_mvmap(). 140 | delta({?TYPE, _}=A, B) -> 141 | state_type:delta(A, B). 142 | 143 | -spec encode(state_type:format(), state_mvmap()) -> binary(). 144 | encode(erlang, {?TYPE, _}=CRDT) -> 145 | erlang:term_to_binary(CRDT). 146 | 147 | -spec decode(state_type:format(), binary()) -> state_mvmap(). 148 | decode(erlang, Binary) -> 149 | {?TYPE, _} = CRDT = erlang:binary_to_term(Binary), 150 | CRDT. 151 | 152 | 153 | %% =================================================================== 154 | %% EUnit tests 155 | %% =================================================================== 156 | -ifdef(TEST). 157 | 158 | set_test() -> 159 | ActorOne = 1, 160 | ActorTwo = 2, 161 | Map0 = new(), 162 | {ok, Map1} = mutate({set, "a", "a_value"}, ActorOne, Map0), 163 | {ok, Map2} = mutate({set, "b", "b_value"}, ActorOne, Map1), 164 | {ok, Map3} = mutate({set, "c", "c1_value"}, ActorOne, Map2), 165 | {ok, Map4} = mutate({set, "c", "c2_value"}, ActorTwo, Map2), 166 | Map5 = merge(Map3, Map4), 167 | {ok, Map6} = mutate({set, "c", "c_value"}, ActorOne, Map5), 168 | ?assertEqual([{"a", sets:from_list(["a_value"])}], query(Map1)), 169 | ?assertEqual([{"a", sets:from_list(["a_value"])}, {"b", sets:from_list(["b_value"])}], query(Map2)), 170 | ?assertEqual([{"a", sets:from_list(["a_value"])}, {"b", sets:from_list(["b_value"])}, {"c", sets:from_list(["c1_value"])}], query(Map3)), 171 | ?assertEqual([{"a", sets:from_list(["a_value"])}, {"b", sets:from_list(["b_value"])}, {"c", sets:from_list(["c2_value"])}], query(Map4)), 172 | ?assertEqual([{"a", sets:from_list(["a_value"])}, {"b", sets:from_list(["b_value"])}, {"c", sets:from_list(["c1_value", "c2_value"])}], query(Map5)), 173 | ?assertEqual([{"a", sets:from_list(["a_value"])}, {"b", sets:from_list(["b_value"])}, {"c", sets:from_list(["c_value"])}], query(Map6)). 174 | 175 | -endif. 176 | -------------------------------------------------------------------------------- /src/state_type.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(state_type). 21 | -author("Vitor Enes Duarte "). 22 | 23 | -include("state_type.hrl"). 24 | 25 | -export([new/1, 26 | mutate/3, 27 | is_inflation/2, 28 | is_strict_inflation/2, 29 | irreducible_is_strict_inflation/2]). 30 | -export([delta/2]). 31 | -export([extract_args/1]). 32 | -export([crdt_size/1]). 33 | 34 | -export_type([state_type/0, 35 | crdt/0, 36 | digest/0, 37 | format/0, 38 | delta_method/0]). 39 | 40 | %% Define some initial types. 41 | -type state_type() :: state_awmap | 42 | state_awset | 43 | state_awset_ps | 44 | state_bcounter | 45 | state_boolean | 46 | state_dwflag | 47 | state_ewflag | 48 | state_gcounter | 49 | state_gmap | 50 | state_gset | 51 | state_ivar | 52 | state_lexcounter | 53 | state_lwwregister | 54 | state_max_int | 55 | state_mvregister | 56 | state_mvmap | 57 | state_orset | 58 | state_pair | 59 | state_pncounter | 60 | state_twopset. 61 | -type crdt() :: {state_type(), type:payload()}. 62 | -type digest() :: {state, crdt()} | %% state as digest 63 | {mdata, term()}. %% metadata as digest 64 | -type delta_method() :: state | mdata. 65 | 66 | %% Supported serialization formats. 67 | -type format() :: erlang. 68 | 69 | %% Perform a delta mutation. 70 | -callback delta_mutate(type:operation(), type:id(), crdt()) -> 71 | {ok, crdt()} | {error, type:error()}. 72 | 73 | %% Merge two replicas. 74 | %% If we merge two CRDTs, the result is a CRDT. 75 | %% If we merge a delta and a CRDT, the result is a CRDT. 76 | %% If we merge two deltas, the result is a delta (delta group). 77 | -callback merge(crdt(), crdt()) -> crdt(). 78 | 79 | %% Check if a some state is bottom 80 | -callback is_bottom(crdt()) -> boolean(). 81 | 82 | %% Inflation testing. 83 | -callback is_inflation(crdt(), crdt()) -> boolean(). 84 | -callback is_strict_inflation(crdt(), crdt()) -> boolean(). 85 | 86 | %% Let A be the first argument. 87 | %% Let B be the second argument. 88 | %% A is a join-irreducible state. 89 | %% This functions checks if A will strictly inflate B. 90 | %% B can be a CRDT or a digest of a CRDT. 91 | -callback irreducible_is_strict_inflation(crdt(), 92 | digest()) -> boolean(). 93 | 94 | %% CRDT digest (which can be the CRDT state itself). 95 | -callback digest(crdt()) -> digest(). 96 | 97 | %% Join decomposition. 98 | -callback join_decomposition(crdt()) -> [crdt()]. 99 | 100 | %% Let A be the first argument. 101 | %% Let B be the second argument. 102 | %% This function returns a ∆ from A that inflates B. 103 | %% "The join of all s in join_decomposition(A) such that s strictly 104 | %% inflates B" 105 | -callback delta(crdt(), digest()) -> crdt(). 106 | 107 | %% @todo These should be moved to type.erl 108 | %% Encode and Decode. 109 | -callback encode(format(), crdt()) -> binary(). 110 | -callback decode(format(), binary()) -> crdt(). 111 | 112 | %% @doc Builds a new CRDT from a given CRDT 113 | -spec new(crdt()) -> any(). %% @todo Fix this any() 114 | new({?GMAP_TYPE, {ValuesType, _Payload}}) -> 115 | ?GMAP_TYPE:new([ValuesType]); 116 | new({?PAIR_TYPE, {Fst, Snd}}) -> 117 | {?PAIR_TYPE, {new(Fst), new(Snd)}}; 118 | new({Type, _Payload}) -> 119 | Type:new(). 120 | 121 | %% @doc Generic Join composition. 122 | -spec mutate(type:operation(), type:id(), crdt()) -> 123 | {ok, crdt()} | {error, type:error()}. 124 | mutate(Op, Actor, {Type, _}=CRDT) -> 125 | case Type:delta_mutate(Op, Actor, CRDT) of 126 | {ok, {Type, Delta}} -> 127 | {ok, Type:merge({Type, Delta}, CRDT)}; 128 | Error -> 129 | Error 130 | end. 131 | 132 | %% @doc Generic check for inflation. 133 | -spec is_inflation(crdt(), crdt()) -> boolean(). 134 | is_inflation({Type, _}=CRDT1, {Type, _}=CRDT2) -> 135 | Type:equal(Type:merge(CRDT1, CRDT2), CRDT2). 136 | 137 | %% @doc Generic check for strict inflation. 138 | %% We have a strict inflation if: 139 | %% - we have an inflation 140 | %% - we have different CRDTs 141 | -spec is_strict_inflation(crdt(), crdt()) -> boolean(). 142 | is_strict_inflation({Type, _}=CRDT1, {Type, _}=CRDT2) -> 143 | Type:is_inflation(CRDT1, CRDT2) andalso 144 | not Type:equal(CRDT1, CRDT2). 145 | 146 | %% @doc Generic check for irreducible strict inflation. 147 | -spec irreducible_is_strict_inflation(crdt(), 148 | digest()) -> boolean(). 149 | irreducible_is_strict_inflation({Type, _}=Irreducible, 150 | {state, {Type, _}=CRDT}) -> 151 | Merged = Type:merge(Irreducible, CRDT), 152 | Type:is_strict_inflation(CRDT, Merged). 153 | 154 | %% @doc Generic delta calculation. 155 | -spec delta(crdt(), digest()) -> crdt(). 156 | delta({Type, _}=A, B) -> 157 | lists:foldl( 158 | fun(Irreducible, Acc) -> 159 | case Type:irreducible_is_strict_inflation(Irreducible, 160 | B) of 161 | true -> 162 | Type:merge(Irreducible, Acc); 163 | false -> 164 | Acc 165 | end 166 | end, 167 | new(A), 168 | Type:join_decomposition(A) 169 | ). 170 | 171 | %% @doc extract arguments from complex (composite) types 172 | extract_args({Type, Args}) -> 173 | {Type, Args}; 174 | extract_args(Type) -> 175 | {Type, []}. 176 | 177 | %% @doc Term size. 178 | crdt_size({?AWMAP_TYPE, {_CType, CRDT}}) -> crdt_size(CRDT); 179 | crdt_size({?AWSET_TYPE, CRDT}) -> crdt_size(CRDT); 180 | crdt_size({?BCOUNTER_TYPE, {CRDT1, CRDT2}}) -> 181 | crdt_size(CRDT1) + crdt_size(CRDT2); 182 | crdt_size({?BOOLEAN_TYPE, CRDT}) -> crdt_size(CRDT); 183 | crdt_size({?DWFLAG_TYPE, CRDT}) -> crdt_size(CRDT); 184 | crdt_size({?EWFLAG_TYPE, CRDT}) -> crdt_size(CRDT); 185 | crdt_size({?GCOUNTER_TYPE, CRDT}) -> crdt_size(CRDT); 186 | crdt_size({?GMAP_TYPE, {_CType, CRDT}}) -> crdt_size(CRDT); 187 | crdt_size({?GSET_TYPE, CRDT}) -> crdt_size(CRDT); 188 | crdt_size({?IVAR_TYPE, CRDT}) -> crdt_size(CRDT); 189 | crdt_size({?LEXCOUNTER_TYPE, CRDT}) -> crdt_size(CRDT); 190 | crdt_size({?LWWREGISTER_TYPE, CRDT}) -> crdt_size(CRDT); 191 | crdt_size({?MAX_INT_TYPE, CRDT}) -> crdt_size(CRDT); 192 | crdt_size({?MVMAP_TYPE, CRDT}) -> crdt_size(CRDT); 193 | crdt_size({?MVREGISTER_TYPE, CRDT}) -> crdt_size(CRDT); 194 | crdt_size({?ORSET_TYPE, CRDT}) -> crdt_size(CRDT); 195 | crdt_size({?PAIR_TYPE, {CRDT1, CRDT2}}) -> 196 | crdt_size(CRDT1) + crdt_size(CRDT2); 197 | crdt_size({?PNCOUNTER_TYPE, CRDT}) -> crdt_size(CRDT); 198 | crdt_size({?TWOPSET_TYPE, CRDT}) -> crdt_size(CRDT); 199 | crdt_size(T) -> 200 | erts_debug:flat_size(T). 201 | -------------------------------------------------------------------------------- /src/type.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2015-2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | %% @doc Inspired by both the Lasp data type behaviour and the Riak data 21 | %% type behaviour. 22 | 23 | -module(type). 24 | -author("Christopher Meiklejohn "). 25 | 26 | -export_type([crdt/0, 27 | id/0, 28 | payload/0, 29 | operation/0, 30 | error/0]). 31 | 32 | %% Define some initial types. 33 | -type type() :: state_type:state_type() | pure_type:pure_type(). 34 | -type payload() :: term(). 35 | -type crdt() :: {type(), payload()}. 36 | -type operation() :: term(). 37 | -type id() :: term(). 38 | -type value() :: term(). 39 | -type error() :: term(). 40 | 41 | %% Initialize a CRDT. 42 | -callback new() -> crdt(). 43 | 44 | %% Unified interface for allowing parameterized CRDTs. 45 | -callback new([term()]) -> crdt(). 46 | 47 | %% Perform a mutation. 48 | -callback mutate(operation(), id(), crdt()) -> 49 | {ok, crdt()} | {error, error()}. 50 | 51 | %% Get the value of a CRDT. 52 | -callback query(crdt()) -> value(). 53 | 54 | %% Compare equality. 55 | -callback equal(crdt(), crdt()) -> boolean(). 56 | -------------------------------------------------------------------------------- /src/types.app.src: -------------------------------------------------------------------------------- 1 | {application,types, 2 | [{description,"Conflict-free Replicated Data Types"}, 3 | {vsn,"0.1.8"}, 4 | {registered,[]}, 5 | {applications,[kernel,stdlib,crypto]}, 6 | {mod,{types_app,[]}}, 7 | {modules,[]}, 8 | {maintainers,["Chris Meiklejohn","Vitor Enes Duarte"]}, 9 | {licenses,["Apache 2.0"]}, 10 | {links,[{"Github","https://github.com/lasp-lang/types"}]}]}. 11 | -------------------------------------------------------------------------------- /src/types.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(types). 21 | -author("Christopher S. Meiklejohn "). 22 | 23 | -export([start/0, 24 | stop/0]). 25 | 26 | %% @doc Start the application. 27 | start() -> 28 | application:ensure_all_started(types). 29 | 30 | %% @doc Stop the application. 31 | stop() -> 32 | application:stop(types). 33 | -------------------------------------------------------------------------------- /src/types_app.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(types_app). 21 | -author("Christopher S. Meiklejohn 29 | case types_sup:start_link() of 30 | {ok, Pid} -> 31 | {ok, Pid}; 32 | Other -> 33 | {error, Other} 34 | end. 35 | 36 | %% @doc Stop the application. 37 | stop(_State) -> 38 | ok. 39 | -------------------------------------------------------------------------------- /src/types_sup.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 3 | %% 4 | %% This file is provided to you under the Apache License, 5 | %% Version 2.0 (the "License"); you may not use this file 6 | %% except in compliance with the License. You may obtain 7 | %% a copy of the License at 8 | %% 9 | %% http://www.apache.org/licenses/LICENSE-2.0 10 | %% 11 | %% Unless required by applicable law or agreed to in writing, 12 | %% software distributed under the License is distributed on an 13 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | %% KIND, either express or implied. See the License for the 15 | %% specific language governing permissions and limitations 16 | %% under the License. 17 | %% 18 | %% ------------------------------------------------------------------- 19 | 20 | -module(types_sup). 21 | -author("Christopher S. Meiklejohn "). 22 | 23 | -behaviour(supervisor). 24 | 25 | -export([start_link/0]). 26 | 27 | -export([init/1]). 28 | 29 | -define(CHILD(I, Type, Timeout), 30 | {I, {I, start_link, []}, permanent, Timeout, Type, [I]}). 31 | -define(CHILD(I, Type), ?CHILD(I, Type, 5000)). 32 | 33 | start_link() -> 34 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 35 | 36 | init([]) -> 37 | RestartStrategy = {one_for_one, 10, 10}, 38 | {ok, {RestartStrategy, []}}. 39 | -------------------------------------------------------------------------------- /test/prop_causal_context.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(prop_causal_context). 23 | -author("Vitor Enes Duarte "). 24 | 25 | -include_lib("proper/include/proper.hrl"). 26 | -include_lib("eunit/include/eunit.hrl"). 27 | 28 | -define(ACTOR, oneof([a, b, c])). 29 | -define(DOT, {?ACTOR, dot_store:dot_sequence()}). 30 | -define(DOTL, list(?DOT)). 31 | 32 | prop_from_dots() -> 33 | ?FORALL( 34 | L, 35 | ?DOTL, 36 | begin 37 | %% if we construct a cc from a list of dots, 38 | %% all of those dots should be there 39 | CC = cc(L), 40 | lists:foldl( 41 | fun(Dot, Acc) -> 42 | Acc andalso causal_context:is_element(Dot, CC) 43 | end, 44 | true, 45 | L 46 | ) 47 | end 48 | ). 49 | 50 | prop_add_dot() -> 51 | ?FORALL( 52 | {Dot, L}, 53 | {?DOT, ?DOTL}, 54 | begin 55 | %% if we add a dot to a cc it should be there 56 | CC = cc(L), 57 | causal_context:is_element( 58 | Dot, 59 | causal_context:add_dot(Dot, CC) 60 | ) 61 | end 62 | ). 63 | 64 | prop_next_dot() -> 65 | ?FORALL( 66 | {Actor, L}, 67 | {?ACTOR, ?DOTL}, 68 | begin 69 | %% the next dot should not be part of the cc. 70 | CC = cc(L), 71 | Dot = causal_context:next_dot(Actor, CC), 72 | not causal_context:is_element( 73 | Dot, 74 | CC 75 | ) 76 | end 77 | ). 78 | 79 | prop_union() -> 80 | ?FORALL( 81 | {L1, L2}, 82 | {?DOTL, ?DOTL}, 83 | begin 84 | CC1 = cc(L1), 85 | CC2 = cc(L2), 86 | Union = causal_context:union(CC1, CC2), 87 | 88 | %% Dots from the cc's belong to the union. 89 | R1 = dot_set:fold( 90 | fun(Dot, Acc) -> 91 | Acc andalso 92 | causal_context:is_element(Dot, Union) 93 | end, 94 | true, 95 | dot_set:union(causal_context:dots(CC1), 96 | causal_context:dots(CC2)) 97 | ), 98 | 99 | %% Dots from the union belong to one of the cc's. 100 | R2 = dot_set:fold( 101 | fun(Dot, Acc) -> 102 | Acc andalso 103 | ( 104 | causal_context:is_element(Dot, CC1) orelse 105 | causal_context:is_element(Dot, CC2) 106 | ) 107 | end, 108 | true, 109 | causal_context:dots(Union) 110 | ), 111 | 112 | %% Dots in the DotSet don't belong in the compressed part 113 | {Compressed, DotSet} = Union, 114 | FakeUnion = {Compressed, dot_set:new()}, 115 | R3 = dot_set:fold( 116 | fun(Dot, Acc) -> 117 | Acc andalso 118 | not causal_context:is_element(Dot, FakeUnion) 119 | end, 120 | true, 121 | DotSet 122 | ), 123 | 124 | R1 andalso R2 andalso R3 125 | end 126 | ). 127 | 128 | %% @private 129 | cc(L) -> 130 | lists:foldl( 131 | fun(Dot, CC) -> 132 | causal_context:add_dot(Dot, CC) 133 | end, 134 | causal_context:new(), 135 | shuffle(L) 136 | ). 137 | 138 | %% @private 139 | shuffle(L) -> 140 | rand:seed(exsplus, erlang:timestamp()), 141 | lists:map( 142 | fun({_, E}) -> E end, 143 | lists:sort( 144 | lists:map( 145 | fun(E) -> {rand:uniform(), E} end, L 146 | ) 147 | ) 148 | ). 149 | -------------------------------------------------------------------------------- /test/prop_dot_set.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(prop_dot_set). 23 | -author("Vitor Enes Duarte "). 24 | 25 | -include_lib("proper/include/proper.hrl"). 26 | -include_lib("eunit/include/eunit.hrl"). 27 | 28 | -define(ACTOR, oneof([a, b, c])). 29 | -define(DOT, {?ACTOR, dot_store:dot_sequence()}). 30 | -define(DOTL, list(?DOT)). 31 | 32 | prop_add_dot() -> 33 | ?FORALL( 34 | {Dot, L}, 35 | {?DOT, ?DOTL}, 36 | begin 37 | %% if we add a dot to a ds it should be there 38 | DS = ds(L), 39 | dot_set:is_element( 40 | Dot, 41 | dot_set:add_dot(Dot, DS) 42 | ) 43 | end 44 | ). 45 | 46 | prop_union() -> 47 | ?FORALL( 48 | {L1, L2}, 49 | {?DOTL, ?DOTL}, 50 | begin 51 | DS1 = ds(L1), 52 | DS2 = ds(L2), 53 | Union = dot_set:union(DS1, DS2), 54 | 55 | %% Dots from the ds's belong to the union. 56 | R1 = dot_set:fold( 57 | fun(Dot, Acc) -> 58 | Acc andalso 59 | dot_set:is_element(Dot, Union) 60 | end, 61 | true, 62 | dot_set:union(DS1, DS2) 63 | ), 64 | 65 | %% Dots from the union belong to one of the ds's. 66 | R2 = dot_set:fold( 67 | fun(Dot, Acc) -> 68 | Acc andalso 69 | ( 70 | dot_set:is_element(Dot, DS1) orelse 71 | dot_set:is_element(Dot, DS2) 72 | ) 73 | end, 74 | true, 75 | Union 76 | ), 77 | 78 | R1 andalso R2 79 | end 80 | ). 81 | 82 | prop_intersection() -> 83 | ?FORALL( 84 | {L1, L2}, 85 | {?DOTL, ?DOTL}, 86 | begin 87 | DS1 = ds(L1), 88 | DS2 = ds(L2), 89 | Intersection = dot_set:intersection(DS1, DS2), 90 | 91 | %% Dots from the intersection belong to one of the ds's. 92 | dot_set:fold( 93 | fun(Dot, Acc) -> 94 | Acc andalso 95 | ( 96 | dot_set:is_element(Dot, DS1) andalso 97 | dot_set:is_element(Dot, DS2) 98 | ) 99 | end, 100 | true, 101 | Intersection 102 | ) 103 | end 104 | ). 105 | 106 | prop_subtract() -> 107 | ?FORALL( 108 | {L1, L2}, 109 | {?DOTL, ?DOTL}, 110 | begin 111 | DS1 = ds(L1), 112 | DS2 = ds(L2), 113 | Subtract = dot_set:subtract(DS1, DS2), 114 | dot_set:fold( 115 | fun(Dot, Acc) -> 116 | Acc andalso not dot_set:is_element(Dot, Subtract) 117 | end, 118 | true, 119 | DS2 120 | ) 121 | end 122 | ). 123 | 124 | prop_subtract_causal_context() -> 125 | ?FORALL( 126 | {L1, L2}, 127 | {?DOTL, ?DOTL}, 128 | begin 129 | DS = ds(L1), 130 | CC = cc(L2), 131 | Subtract = dot_set:subtract_causal_context(DS, CC), 132 | dot_set:fold( 133 | fun(Dot, Acc) -> 134 | Acc andalso not dot_set:is_element(Dot, Subtract) 135 | end, 136 | true, 137 | causal_context:dots(CC) 138 | ) 139 | end 140 | ). 141 | 142 | prop_set_laws() -> 143 | ?FORALL( 144 | {L1, L2}, 145 | {?DOTL, ?DOTL}, 146 | begin 147 | DS1 = ds(L1), 148 | DS2 = ds(L2), 149 | 150 | %% idempotency 151 | R1 = dot_set:union(DS1, DS1) == DS1, 152 | R2 = dot_set:intersection(DS1, DS1) == DS1, 153 | %% domination 154 | R3 = dot_set:union(DS1, dot_set:new()) == DS1, 155 | R4 = dot_set:intersection(DS1, dot_set:new()) == dot_set:new(), 156 | %% absorption 157 | R5 = dot_set:union(DS1, dot_set:intersection(DS1, DS2)) == DS1, 158 | R6 = dot_set:intersection(DS1, dot_set:union(DS1, DS2)) == DS1, 159 | 160 | R1 andalso R2 161 | andalso R3 162 | andalso R4 163 | andalso R5 164 | andalso R6 165 | end 166 | ). 167 | 168 | %% @private 169 | ds(L) -> 170 | dot_set:from_dots(L). 171 | 172 | %% @private 173 | cc(L) -> 174 | causal_context:from_dot_set(ds(L)). 175 | -------------------------------------------------------------------------------- /test/prop_join_decompositions.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(prop_join_decompositions). 23 | -author("Vitor Enes Duarte "). 24 | 25 | -include_lib("proper/include/proper.hrl"). 26 | -include_lib("eunit/include/eunit.hrl"). 27 | 28 | -include("state_type.hrl"). 29 | 30 | %% common 31 | -define(ACTOR, oneof([a, b, c])). 32 | -define(P(T, Actor), {T, Actor}). 33 | -define(PA(T), ?P(T, a)). 34 | -define(PB(T), ?P(T, b)). 35 | -define(L(T), list(?P(T, ?ACTOR))). 36 | 37 | %% primitives 38 | -define(TRUE, true). 39 | 40 | %% counters 41 | -define(INC, increment). 42 | -define(DEC, decrement). 43 | -define(INCDEC, oneof([?INC, ?DEC])). 44 | 45 | %% sets 46 | -define(ELEMENT, oneof([1, 2, 3])). 47 | -define(ADD, {add, ?ELEMENT}). 48 | -define(RMV, {rmv, ?ELEMENT}). 49 | -define(ADDRMV, oneof([?ADD, ?RMV])). 50 | 51 | %% registers 52 | -define(TIMESTAMP, non_neg_integer()). 53 | -define(SET, {set, ?TIMESTAMP, ?ELEMENT}). 54 | 55 | 56 | %% primitives 57 | prop_boolean_decomposition() -> 58 | ?FORALL(L, ?L(?TRUE), 59 | check_decomposition(create(?BOOLEAN_TYPE, L))). 60 | prop_boolean_redundant() -> 61 | ?FORALL(L, ?L(?TRUE), 62 | check_redundant(create(?BOOLEAN_TYPE, L))). 63 | prop_boolean_irreducible() -> 64 | ?FORALL({L, A, B}, {?L(?TRUE), ?PA(?TRUE), ?PB(?TRUE)}, 65 | check_irreducible(create(?BOOLEAN_TYPE, L), 66 | create(?BOOLEAN_TYPE, [A]), 67 | create(?BOOLEAN_TYPE, [B])) 68 | ). 69 | 70 | prop_max_int_decomposition() -> 71 | ?FORALL(L, ?L(?INC), 72 | check_decomposition(create(?MAX_INT_TYPE, L))). 73 | prop_max_int_redundant() -> 74 | ?FORALL(L, ?L(?INC), 75 | check_redundant(create(?MAX_INT_TYPE, L))). 76 | prop_max_int_irreducible() -> 77 | ?FORALL({L, A, B}, {?L(?INC), ?PA(?INC), ?PB(?INC)}, 78 | check_irreducible(create(?MAX_INT_TYPE, L), 79 | create(?MAX_INT_TYPE, [A]), 80 | create(?MAX_INT_TYPE, [B])) 81 | ). 82 | 83 | 84 | %% counters 85 | prop_gcounter_decomposition() -> 86 | ?FORALL(L, ?L(?INC), 87 | check_decomposition(create(?GCOUNTER_TYPE, L))). 88 | prop_gcounter_redundant() -> 89 | ?FORALL(L, ?L(?INC), 90 | check_redundant(create(?GCOUNTER_TYPE, L))). 91 | prop_gcounter_irreducible() -> 92 | ?FORALL({L, A, B}, {?L(?INC), ?PA(?INC), ?PB(?INC)}, 93 | check_irreducible(create(?GCOUNTER_TYPE, L), 94 | create(?GCOUNTER_TYPE, [A]), 95 | create(?GCOUNTER_TYPE, [B])) 96 | ). 97 | 98 | prop_pncounter_decomposition() -> 99 | ?FORALL(L, ?L(?INCDEC), 100 | check_decomposition(create(?PNCOUNTER_TYPE, L))). 101 | prop_pncounter_redundant() -> 102 | ?FORALL(L, ?L(?INCDEC), 103 | check_redundant(create(?PNCOUNTER_TYPE, L))). 104 | prop_pncounter_irreducible() -> 105 | ?FORALL({L, A, B}, {?L(?INCDEC), ?PA(?INCDEC), ?PB(?INCDEC)}, 106 | check_irreducible(create(?PNCOUNTER_TYPE, L), 107 | create(?PNCOUNTER_TYPE, [A]), 108 | create(?PNCOUNTER_TYPE, [B])) 109 | ). 110 | 111 | %% sets 112 | prop_gset_decomposition() -> 113 | ?FORALL(L, ?L(?ADD), 114 | check_decomposition(create(?GSET_TYPE, L))). 115 | prop_gset_redundant() -> 116 | ?FORALL(L, ?L(?ADD), 117 | check_redundant(create(?GSET_TYPE, L))). 118 | prop_gset_irreducible() -> 119 | ?FORALL({L, A, B}, {?L(?ADD), ?PA(?ADD), ?PB(?ADD)}, 120 | check_irreducible(create(?GSET_TYPE, L), 121 | create(?GSET_TYPE, [A]), 122 | create(?GSET_TYPE, [B])) 123 | ). 124 | prop_gset_digest() -> 125 | ?FORALL({L1, L2}, {?L(?ADD), ?L(?ADD)}, 126 | check_digest(create(?GSET_TYPE, L1), 127 | create(?GSET_TYPE, L2))). 128 | 129 | prop_twopset_decomposition() -> 130 | ?FORALL(L, ?L(?ADDRMV), 131 | check_decomposition(create(?TWOPSET_TYPE, L))). 132 | prop_twopset_redundant() -> 133 | ?FORALL(L, ?L(?ADDRMV), 134 | check_redundant(create(?TWOPSET_TYPE, L))). 135 | prop_twopset_irreducible() -> 136 | ?FORALL({L, A, B}, {?L(?ADDRMV), ?PA(?ADDRMV), ?PB(?ADDRMV)}, 137 | check_irreducible(create(?TWOPSET_TYPE, L), 138 | create(?TWOPSET_TYPE, [A]), 139 | create(?TWOPSET_TYPE, [B])) 140 | ). 141 | 142 | prop_awset_decomposition() -> 143 | ?FORALL(L, ?L(?ADDRMV), 144 | check_decomposition(create(?AWSET_TYPE, L))). 145 | prop_awset_redundant() -> 146 | ?FORALL(L, ?L(?ADDRMV), 147 | check_redundant(create(?AWSET_TYPE, L))). 148 | prop_awset_irreducible() -> 149 | ?FORALL({L, A, B}, {?L(?ADDRMV), ?PA(?ADDRMV), ?PB(?ADDRMV)}, 150 | check_irreducible(create(?AWSET_TYPE, L), 151 | create(?AWSET_TYPE, [A]), 152 | create(?AWSET_TYPE, [B])) 153 | ). 154 | prop_awset_digest() -> 155 | ?FORALL({L1, L2}, {?L(?ADDRMV), ?L(?ADDRMV)}, 156 | check_digest(create(?AWSET_TYPE, L1), 157 | create(?AWSET_TYPE, L2))). 158 | 159 | prop_orset_decomposition() -> 160 | ?FORALL(L, ?L(?ADDRMV), 161 | check_decomposition(create(?ORSET_TYPE, L))). 162 | prop_orset_redundant() -> 163 | ?FORALL(L, ?L(?ADDRMV), 164 | check_redundant(create(?ORSET_TYPE, L))). 165 | prop_orset_irreducible() -> 166 | ?FORALL({L, A, B}, {?L(?ADDRMV), ?PA(?ADDRMV), ?PB(?ADDRMV)}, 167 | check_irreducible(create(?ORSET_TYPE, L), 168 | create(?ORSET_TYPE, [A]), 169 | create(?ORSET_TYPE, [B])) 170 | ). 171 | 172 | %% registers 173 | prop_lwwregister_decomposition() -> 174 | ?FORALL(L, ?L(?SET), 175 | check_decomposition(create(?LWWREGISTER_TYPE, L))). 176 | prop_lwwregister_redundant() -> 177 | ?FORALL(L, ?L(?SET), 178 | check_redundant(create(?LWWREGISTER_TYPE, L))). 179 | prop_lwwregister_irreducible() -> 180 | ?FORALL({L, A, B}, {?L(?SET), ?PA(?SET), ?PB(?SET)}, 181 | check_irreducible(create(?LWWREGISTER_TYPE, L), 182 | create(?LWWREGISTER_TYPE, [A]), 183 | create(?LWWREGISTER_TYPE, [B])) 184 | ). 185 | 186 | prop_mvregister_decomposition() -> 187 | ?FORALL(L, ?L(?SET), 188 | check_decomposition(create(?MVREGISTER_TYPE, L))). 189 | prop_mvregister_redundant() -> 190 | ?FORALL(L, ?L(?SET), 191 | check_redundant(create(?MVREGISTER_TYPE, L))). 192 | prop_mvregister_irreducible() -> 193 | %% @todo 194 | true. 195 | 196 | 197 | %% @private 198 | check_decomposition({Type, _}=CRDT) -> 199 | Bottom = state_type:new(CRDT), 200 | 201 | %% the join of the decomposition should given the CRDT 202 | JD = Type:join_decomposition(CRDT), 203 | Merged = merge_all(Bottom, JD), 204 | Type:equal(CRDT, Merged). 205 | 206 | %% @private 207 | check_redundant({Type, _}=CRDT) -> 208 | Bottom = state_type:new(CRDT), 209 | 210 | ?IMPLIES( 211 | not Type:is_bottom(CRDT), 212 | begin 213 | JD = Type:join_decomposition(CRDT), 214 | 215 | %% if we remove one element from the decomposition, 216 | %% the rest is not enough to produce the CRDT 217 | Random = rand:uniform(length(JD)), 218 | Element = lists:nth(Random, JD), 219 | Rest = merge_all(Bottom, JD -- [Element]), 220 | Type:is_strict_inflation(Rest, CRDT) 221 | end 222 | ). 223 | 224 | %% @private 225 | check_irreducible({Type, _}=CRDT, A, B) -> 226 | Merged = Type:merge(A, B), 227 | Tests = lists:map( 228 | fun(Irreducible) -> 229 | 230 | %% check that all the elements in the join decomposition 231 | %% are join-irreducible 232 | Test = ?IMPLIES( 233 | Type:equal(Merged, Irreducible), 234 | Type:equal(A, Irreducible) orelse 235 | Type:equal(B, Irreducible) 236 | ), 237 | 238 | {Irreducible, Test} 239 | end, 240 | Type:join_decomposition(CRDT) 241 | ), 242 | 243 | conjunction(Tests). 244 | 245 | %% @private 246 | check_digest({Type, _}=CRDT1, {Type, _}=CRDT2) -> 247 | 248 | %% the delta of two states, 249 | %% using the original state 250 | %% or a digest of that state, 251 | %% should be the same delta 252 | DeltaState = Type:delta(CRDT1, {state, CRDT2}), 253 | DeltaDigest = Type:delta(CRDT1, Type:digest(CRDT2)), 254 | 255 | Type:equal(DeltaState, DeltaDigest). 256 | 257 | %% @private 258 | merge_all({Type, _}=Bottom, CRDTList) -> 259 | lists:foldl( 260 | fun(CRDT, Acc) -> 261 | Type:merge(CRDT, Acc) 262 | end, 263 | Bottom, 264 | CRDTList 265 | ). 266 | 267 | %% @private 268 | create(Type, L) -> 269 | %% get list of actors 270 | Actors = lists:usort([Actor || {_Op, Actor} <- L]), 271 | 272 | %% create an dictionary from actors to list of ops 273 | OpsPerActor = lists:foldl( 274 | fun(Actor, Acc) -> 275 | Ops = lists:filtermap( 276 | fun({Op, OpActor}) -> 277 | case Actor == OpActor of 278 | true -> 279 | {true, Op}; 280 | false -> 281 | false 282 | end 283 | end, 284 | L 285 | ), 286 | orddict:store(Actor, Ops, Acc) 287 | end, 288 | orddict:new(), 289 | Actors 290 | ), 291 | 292 | %% create a CRDT per actor, by applying its list of ops 293 | CRDTList = orddict:fold( 294 | fun(Actor, Ops, Acc) -> 295 | CRDT = lists:foldl( 296 | fun(Op, CRDT0) -> 297 | {ok, CRDT1} = Type:mutate(Op, Actor, CRDT0), 298 | CRDT1 299 | end, 300 | Type:new(), 301 | Ops 302 | ), 303 | 304 | [CRDT | Acc] 305 | end, 306 | [], 307 | OpsPerActor 308 | ), 309 | 310 | %% merge the list of CRDTs 311 | merge_all(Type:new(), CRDTList). 312 | -------------------------------------------------------------------------------- /test/state_type_semantics_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% ------------------------------------------------------------------- 2 | %% 3 | %% Copyright (c) 2016 Christopher Meiklejohn. All Rights Reserved. 4 | %% 5 | %% This file is provided to you under the Apache License, 6 | %% Version 2.0 (the "License"); you may not use this file 7 | %% except in compliance with the License. You may obtain 8 | %% a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, 13 | %% software distributed under the License is distributed on an 14 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | %% KIND, either express or implied. See the License for the 16 | %% specific language governing permissions and limitations 17 | %% under the License. 18 | %% 19 | %% ------------------------------------------------------------------- 20 | %% 21 | 22 | -module(state_type_semantics_SUITE). 23 | -author("Vitor Enes Duarte "). 24 | 25 | %% common_test callbacks 26 | -export([init_per_suite/1, 27 | end_per_suite/1, 28 | init_per_testcase/2, 29 | end_per_testcase/2, 30 | all/0]). 31 | 32 | %% tests 33 | -compile([nowarn_export_all, export_all]). 34 | 35 | -include("state_type.hrl"). 36 | 37 | -include_lib("common_test/include/ct.hrl"). 38 | -include_lib("eunit/include/eunit.hrl"). 39 | 40 | %% =================================================================== 41 | %% common_test callbacks 42 | %% =================================================================== 43 | 44 | init_per_suite(Config) -> Config. 45 | end_per_suite(Config) -> Config. 46 | init_per_testcase(_Case, Config) -> Config. 47 | end_per_testcase(_Case, Config) -> Config. 48 | 49 | all() -> 50 | [ 51 | flag_test 52 | ]. 53 | 54 | %% =================================================================== 55 | %% tests 56 | %% =================================================================== 57 | 58 | flag_test(_Config) -> 59 | List = [ 60 | {state_ewflag, true}, 61 | {state_dwflag, false} 62 | ], 63 | 64 | lists:foreach( 65 | fun({FlagType, ExpectedValue}) -> 66 | {Value1, Value2} = flag_mutate_concurrently(FlagType), 67 | ?assert(Value1 == Value2), 68 | ?assertEqual(ExpectedValue, Value1) 69 | end, 70 | List 71 | ). 72 | 73 | %% =================================================================== 74 | %% Internal functions 75 | %% =================================================================== 76 | 77 | flag_mutate_concurrently(FlagType) -> 78 | ActorA = "A", 79 | ActorB = "B", 80 | ActorC = "C", 81 | Flag0 = FlagType:new(), 82 | {ok, EFlag} = FlagType:mutate(enable, ActorC, Flag0), 83 | {ok, DFlag} = FlagType:mutate(disable, ActorC, Flag0), 84 | ?assertEqual(true, FlagType:query(EFlag)), 85 | ?assertEqual(false, FlagType:query(DFlag)), 86 | 87 | %% concurrent mutations in enabled flag 88 | {ok, EFlagA} = FlagType:mutate(enable, ActorA, EFlag), 89 | {ok, EFlagB} = FlagType:mutate(disable, ActorB, EFlag), 90 | ?assertEqual(true, FlagType:query(EFlagA)), 91 | ?assertEqual(false, FlagType:query(EFlagB)), 92 | %% merge them 93 | EFlagMerged = FlagType:merge(EFlagA, EFlagB), 94 | 95 | %% concurrent mutations in disabled flag 96 | {ok, DFlagA} = FlagType:mutate(enable, ActorA, DFlag), 97 | {ok, DFlagB} = FlagType:mutate(disable, ActorB, DFlag), 98 | ?assertEqual(true, FlagType:query(DFlagA)), 99 | ?assertEqual(false, FlagType:query(DFlagB)), 100 | %% merge them 101 | DFlagMerged = FlagType:merge(DFlagA, DFlagB), 102 | 103 | %% return query values 104 | { 105 | FlagType:query(EFlagMerged), 106 | FlagType:query(DFlagMerged) 107 | }. 108 | -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # 3 | # Copyright (c) 2014 Basho Technologies, Inc. 4 | # 5 | # This file is provided to you under the Apache License, 6 | # Version 2.0 (the "License"); you may not use this file 7 | # except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | # ------------------------------------------------------------------- 20 | 21 | # ------------------------------------------------------------------- 22 | # NOTE: This file is is from https://github.com/basho/tools.mk. 23 | # It should not be edited in a project. It should simply be updated 24 | # wholesale when a new version of tools.mk is released. 25 | # ------------------------------------------------------------------- 26 | 27 | REBAR ?= rebar3 28 | REVISION ?= $(shell git rev-parse --short HEAD) 29 | PROJECT ?= $(shell basename `find src -name "*.app.src"` .app.src) 30 | DEP_DIR ?= "deps" 31 | EBIN_DIR ?= "ebin" 32 | 33 | .PHONY: compile-no-deps docs xref dialyzer 34 | 35 | compile-no-deps: 36 | ${REBAR} compile skip_deps=true 37 | 38 | docs: 39 | ${REBAR} doc skip_deps=true 40 | 41 | xref: compile 42 | ${REBAR} xref skip_deps=true 43 | 44 | dialyzer: compile 45 | ${REBAR} dialyzer 46 | --------------------------------------------------------------------------------