diff options
Diffstat (limited to 'system/doc/reference_manual/opaques.xml')
-rw-r--r-- | system/doc/reference_manual/opaques.xml | 93 |
1 files changed, 49 insertions, 44 deletions
diff --git a/system/doc/reference_manual/opaques.xml b/system/doc/reference_manual/opaques.xml index 8fa3bae3e5..f7d633781c 100644 --- a/system/doc/reference_manual/opaques.xml +++ b/system/doc/reference_manual/opaques.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2021</year> - <year>2022</year> + <year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -33,28 +33,30 @@ <section> <title>Opaque Type Aliases</title> - <p>The main use case for opacity in Erlang is to hide the implementation of a data type, enabling evolving the API while minimizing the risk of breaking consumers. The runtime does not check opacity. Dialyzer provides some opacity-checking, but the rest is up to convention. + <p>The main use case for opacity in Erlang is to hide the + implementation of a data type, enabling evolving the API while + minimizing the risk of breaking consumers. The runtime does not + check opacity. Dialyzer provides some opacity-checking, but the + rest is up to convention. </p> <p> - This document explains what Erlang opacity is (and the trade-offs involved) via the example of OTP's - <c>sets:set()</c> - data type. This type - <em>was</em> - defined in `sets` module like this: + This document explains what Erlang opacity is (and the + trade-offs involved) via the example of the <c>sets:set()</c> + data type. This type <em>was</em> defined in the <c>sets</c> + module like this: </p> <code type="erl">-opaque set(Element) :: #set{segs :: segs(Element)}.</code> - <p>OTP 24 changed the definition to the following, in - <url href="https://github.com/erlang/otp/commit/e66941e8d7c47b973dff94c0308ea85a6be1958e">this commit</url> + <p>OTP 24 changed the definition to the following in + <url href="https://github.com/erlang/otp/commit/e66941e8d7c47b973dff94c0308ea85a6be1958e">this commit</url>. </p> <code type="erl">-opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.</code> <p> - And this change was safer and more backwards-compatible than if the type had been defined with - <c>-type</c> - instead of - <c>-opaque</c> - . Here's why: when a module defines an - <c>-opaque</c> - , the contract is that only the defining module should rely on the definition of the type: no other modules should rely on the definition. + And this change was safer and more backwards-compatible than if + the type had been defined with <c>-type</c> instead of + <c>-opaque</c>. Here is why: when a module defines an + <c>-opaque</c>, the contract is that only the defining module + should rely on the definition of the type: no other modules + should rely on the definition. </p> <p> This means that code that pattern-matched on @@ -62,8 +64,8 @@ as a record/tuple technically broke the contract, and opted in to being potentially broken when the definition of <c>set()</c> changed. Before OTP 24, this code printed - <c>ok</c> - . In OTP 24 it may error: + <c>ok</c>. + In OTP 24 it may error: </p> <code type="erl"> case sets:new() of @@ -85,12 +87,9 @@ end. Instead, use functions provided by the module for working with the type. For example, <c>sets</c> module provides - <c>sets:new/0</c> - , - <c>sets:add/2</c> - , - <c>sets:is_element/2</c> - , etc. + <c>sets:new/0</c>, + <c>sets:add/2</c>, + <c>sets:is_element/2</c>, and so on. </item> <item> <c>sets:set(a)</c> @@ -108,34 +107,40 @@ end. </p> <list type="bulleted"> <item> - Since consumers are expected to not rely on the definition of the opaque type, you must provide functions for constructing and querying/deconstructing intances of your opaque type. For example, sets can be constructed with - <c>sets:new/0</c>, <c>sets:from_list/1</c>, <c>sets:add/2</c>, queried with <c>sets:is_element/2</c>, and deconstructed with<c>sets:to_list/1</c>. + Since consumers are expected to not rely on the definition of + the opaque type, you must provide functions for constructing, + querying, and deconstructing instances of your opaque type. For + example, sets can be constructed with <c>sets:new/0</c>, + <c>sets:from_list/1</c>, <c>sets:add/2</c>, queried with + <c>sets:is_element/2</c>, and deconstructed + with<c>sets:to_list/1</c>. </item> <item> - Don't define an opaque with a type variable in parameter position. This breaks the normal and expected behavior that (for example) - <c>my_type(a)</c> is a subtype of <c>my_type(a | b)</c> + Don't define an opaque with a type variable in parameter + position. This breaks the normal and expected behavior that + (for example) <c>my_type(a)</c> is a subtype of <c>my_type(a | + b)</c> </item> <item> Add <seeguide marker="typespec">specs</seeguide> to exported functions that use the opaque type </item> </list> - <p>Note that opaques can be harder to work with for consumers, since the consumer is expected not to pattern-match and must instead use functions that the author of the opaque type provides to use instances of the type.</p> + <p>Note that opaques can be harder to work with for consumers, + since the consumer is expected not to pattern-match and must + instead use functions that the author of the opaque type provides + to use instances of the type.</p> <p> - Also, opacity in Erlang is skin-deep: the runtime does not enforce opacity-checking. So now that sets are implemented in terms of maps, an - <c>is_map</c> - check on a set - <em>will</em> - pass. The opacity rules are only enforced by convention and by additional tooling such as Dialyzer. And this enforcement is not total: For example, determined consumer of - <c>sets</c> - can still do things that reveal the structure of the set, such as by printing, serializing, or using a set as - <c>term()</c> - and then inspecting via functions like - <c>is_map</c> - or - <c>maps:get/2</c> - . And Dialyzer must make some - <url href="https://github.com/erlang/otp/issues/5118">approximations</url> - . Opacity checking has limitations, but is still a vital tool in scalable Erlang development. + Also, opacity in Erlang is skin-deep: the runtime does not + enforce opacity-checking. So now that sets are implemented in + terms of maps, an <c>is_map/1</c> check on a set <em>will</em> + pass. The opacity rules are only enforced by convention and by + additional tooling such as Dialyzer, and this enforcement is not + total. A determined consumer of <c>sets</c> can still reveal the + structure of the set, for example by printing, serializing, or + using a set as a <c>term()</c> and inspecting it via functions + like <c>is_map/1</c> or <c>maps:get/2</c>. Also, Dialyzer must make + some <url + href="https://github.com/erlang/otp/issues/5118">approximations</url>. </p> </section> </chapter> |