<|start_filename|>test/test_optic_array.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_array.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_array).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   optic:get([optic_array:all([strict])],
                             array:from_list([1, 2, 3]))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_array:all([strict])],
                             atom))].

all_put_test_() ->
    [?_assertEqual({ok, array:from_list([4, 4, 4])},
                   optic:put([optic_array:all([strict])],
                             4,
                             array:from_list([1, 2, 3]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_array:all([strict])],
                             4,
                             atom))].

all_create_test() ->
    ?assertEqual({ok, array:new([{default, test}])},
                 optic:put([optic_array:all([strict, {create, test}])],
                           4,
                           atom)).

nth_get_test_() ->
    [?_assertEqual({ok, [1]},
                   optic:get([optic_array:nth(1, [strict])],
                             array:from_list([1, 2, 3]))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_array:nth(1, [strict])],
                             atom))].

nth_put_test_() ->
    [?_assertEqual({ok, array:from_list([4, 2, 3])},
                   optic:put([optic_array:nth(1, [strict])],
                             4,
                             array:from_list([1, 2, 3]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_array:nth(1, [strict])],
                             4,
                             atom))].

nth_create_test_() ->
    [?_assertEqual({ok, [test, 4]},
                   maybe_to_list(
                     optic:put([optic_array:nth(2, [strict, {create, test}])],
                               4,
                               atom))),
     ?_assertEqual({ok, [1, undefined, 4]},
                   maybe_to_list(
                     optic:put([optic_array:nth(3, [strict, create])],
                               4,
                               array:from_list([1]))))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

maybe_to_list({ok, Result}) ->
    {ok, array:to_list(Result)}.


<|start_filename|>test/test_optic_proplists.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_proplists.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_proplists).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 1, 2, 3]},
                   optic:get([optic_proplists:all([strict])],
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {one, 4}, {two, 4}, {three, 4}]},
                   optic:put([optic_proplists:all([strict])],
                             4,
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, []},
                 optic:put([optic_proplists:all([strict, create])], 4, atom)).

keys_get_test_() ->
    [?_assertEqual({ok, [one, one, two, three]},
                   optic:get([optic_proplists:keys([strict])],
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:keys([strict])], atom))].

keys_put_test_() ->
    [?_assertEqual({ok, [{four, 1}, {four, 1}, {four, 2}, {four, 3}]},
                   optic:put([optic_proplists:keys([strict])],
                             four,
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:keys([strict])], four, atom))].

keys_create_test() ->
    ?assertEqual({ok, []},
                 optic:put([optic_proplists:keys([strict, create])],
                           four,
                           atom)).

values_get_test_() ->
    [?_assertEqual({ok, [1, 1, 2, 3]},
                   optic:get([optic_proplists:values([strict])],
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:values([strict])], atom))].

values_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {one, 4}, {two, 4}, {three, 4}]},
                   optic:put([optic_proplists:values([strict])],
                             4,
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:values([strict])], 4, atom))].

values_create_test() ->
    ?assertEqual({ok, []},
                 optic:put([optic_proplists:values([strict, create])],
                           4,
                           atom)).

properties_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {one, 1}, {two, 2}, {three, 3}]},
                   optic:get([optic_proplists:properties([strict])],
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:properties([strict])], atom))].

properties_put_test_() ->
    [?_assertEqual({ok, [{four, 4}, {four, 4}, {four, 4}, {four, 4}]},
                   optic:put([optic_proplists:properties([strict])],
                             {four, 4},
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:properties([strict])],
                             four,
                             atom))].

properties_create_test() ->
    ?assertEqual({ok, []},
                 optic:put([optic_proplists:properties([strict, create])],
                           four,
                           atom)).

key_get_test_() ->
    [?_assertEqual({ok, [1, 1.5]},
                   optic:get([optic_proplists:key(one, [strict])],
                             [{one, 1}, {one, 1.5}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:key(one, [strict])], atom))].

key_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {one, 4}, {two, 2}, {three, 3}]},
                   optic:put([optic_proplists:key(one, [strict])],
                             4,
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:key(one, [strict])], 4, atom))].

key_create_test_() ->
    [?_assertEqual({ok, [{four, 4}, {one, 1}]},
                   optic:put([optic_proplists:key(four, [strict, create])],
                             4,
                             [{one, 1}])),
     ?_assertEqual({ok, [{four, 4}]},
                   optic:put([optic_proplists:key(four, [strict, create])],
                             4,
                             atom))].

property_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {one, 1.5}]},
                   optic:get([optic_proplists:property(one, [strict])],
                             [{one, 1}, {one, 1.5}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_proplists:property(one, [strict])],
                             atom))].

property_put_test_() ->
    [?_assertEqual({ok, [{four, 4}, {four, 4}, {two, 2}, {three, 3}]},
                   optic:put([optic_proplists:property(one, [strict])],
                             {four, 4},
                             [{one, 1}, {one, 1}, {two, 2}, {three, 3}])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_proplists:property(one, [strict])],
                             4,
                             atom))].

property_create_test_() ->
    [?_assertEqual({ok, [{four, 4}, {one, 1}]},
                   optic:put([optic_proplists:property(four, [strict, create])],
                             {four, 4},
                             [{one, 1}])),
     ?_assertEqual({ok, [{four, 4}]},
                   optic:put([optic_proplists:property(four, [strict, create])],
                             {four, 4},
                             atom))].


<|start_filename|>doc/optic.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#types">Data Types</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>   
A library for creating "optics", a composable traversal over   
arbitrary containers with the possibility of error.


<h2><a name="description">Description</a></h2><p>   
A library for creating "optics", a composable traversal over   
arbitrary containers with the possibility of error.</p>
  
   <p>The traversal is modeled as an opaque type containing both a fold   
and mapfold type. As mapfold is a superset of fold, fold is   
implemented for efficiency only. The usual fold and mapfold return   
types are wrapped in ok/error tuples to represent the possibility   
of failure, with the provided compositions being responsible for   
propagating errors back out and skipping further execution.</p>
  
   These optics can then be composed to read and update nested data
   structures. Three types of composition are possible. A wrap, which
   modifies an existing optic into a new form. A chain, which
   combines two optics so that one focuses on the value the previous
   focuses on. Finally, a merge, which combines two optics to allow
   both to focus over the same data.
<h2><a name="types">Data Types</a></h2>

<h3 class="typedecl"><a name="type-callback_filter">callback_filter()</a></h3>
<p><tt>callback_filter() = fun((Elem::term()) -&gt; boolean())</tt></p>
<p>  Callback function invoked by filter optics for each element of a
  container.</p>

<h3 class="typedecl"><a name="type-callback_fold">callback_fold()</a></h3>
<p><tt>callback_fold() = fun((Elem::term(), NewAcc::term()) -&gt; Acc::term())</tt></p>
<p>  Callback function invoked by fold for each element of a container.</p>

<h3 class="typedecl"><a name="type-callback_map">callback_map()</a></h3>
<p><tt>callback_map() = fun((Elem::term()) -&gt; NewElem::term())</tt></p>
<p>  Callback function invoked by map for each element of a container.</p>

<h3 class="typedecl"><a name="type-callback_mapfold">callback_mapfold()</a></h3>
<p><tt>callback_mapfold() = fun((Elem::term(), Acc::term()) -&gt; {NewElem::term(), NewAcc::term()})</tt></p>
<p>  Callback function invoked by mapfold for each element of a
  container.</p>

<h3 class="typedecl"><a name="type-callback_new">callback_new()</a></h3>
<p><tt>callback_new() = fun((Data::term(), Template::term()) -&gt; NewData::term())</tt></p>
<p>  Callback function invoked to create new containers.</p>

<h3 class="typedecl"><a name="type-error">error()</a></h3>
<p><tt>error() = {error, term()}</tt></p>
<p>  The expected error format.</p>

<h3 class="typedecl"><a name="type-optic">optic()</a></h3>
<p><b>abstract datatype</b>: <tt>optic()</tt></p>
<p>  A composable traversal over an arbitrary container.</p>

<h3 class="typedecl"><a name="type-optic_fold">optic_fold()</a></h3>
<p><tt>optic_fold() = fun((Fold::<a href="#type-callback_fold">callback_fold()</a>, Acc::term(), Data::term()) -&gt; <a href="#type-option">option</a>(NewAcc::term()))</tt></p>
<p>  A fold function to be used as part of an optic.</p>

<h3 class="typedecl"><a name="type-optic_mapfold">optic_mapfold()</a></h3>
<p><tt>optic_mapfold() = fun((MapFold::<a href="#type-callback_mapfold">callback_mapfold()</a>, Acc::term(), Data::term()) -&gt; <a href="#type-option">option</a>({NewData::term(), NewAcc::term()}))</tt></p>
<p>  A mapfold function to be used as part of an optic.</p>

<h3 class="typedecl"><a name="type-optic_wrap">optic_wrap()</a></h3>
<p><tt>optic_wrap(Over) = fun((Over) -&gt; Over)</tt></p>
<p>  A mapping function to wrap optics.</p>

<h3 class="typedecl"><a name="type-optic_wrap_fold">optic_wrap_fold()</a></h3>
<p><tt>optic_wrap_fold() = <a href="#type-optic_wrap">optic_wrap</a>(<a href="#type-optic_fold">optic_fold()</a>)</tt></p>
<p>  A mapping function over optic folds.</p>

<h3 class="typedecl"><a name="type-optic_wrap_mapfold">optic_wrap_mapfold()</a></h3>
<p><tt>optic_wrap_mapfold() = <a href="#type-optic_wrap">optic_wrap</a>(<a href="#type-optic_mapfold">optic_mapfold()</a>)</tt></p>
<p>  A mapping function over optic mapfolds.</p>

<h3 class="typedecl"><a name="type-optics">optics()</a></h3>
<p><tt>optics() = [<a href="#type-optic">optic()</a>]</tt></p>
<p>  A list of traversals to be composed.</p>

<h3 class="typedecl"><a name="type-option">option()</a></h3>
<p><tt>option(Success) = {ok, Success} | <a href="#type-error">error()</a></tt></p>
<p>  A result type for operations that might fail.</p>

<h3 class="typedecl"><a name="type-variations">variations()</a></h3>
<p><tt>variations() = #{create =&gt; term(), strict =&gt; boolean(), filter =&gt; <a href="#type-callback_filter">callback_filter()</a>, require =&gt; <a href="#type-callback_filter">callback_filter()</a>} | <a href="proplists.html#type-proplist">proplists:proplist()</a></tt></p>
<p>  Shared options to control optic construction.</p>

<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#chain-1">chain/1</a></td><td>  
Combine existing optics into a chain.</td></tr>
<tr><td valign="top"><a href="#create-3">create/3</a></td><td>
  Wrap an existing optic to cause it to create a new container when
  the optic would otherwise return <code>{error, undefined}</code> or
  <code>{error, required}</code> during a mapfold operation.</td></tr>
<tr><td valign="top"><a href="#error-1">error/1</a></td><td>  
Always errors with the given reason.</td></tr>
<tr><td valign="top"><a href="#filter-1">filter/1</a></td><td>  
Only focuses on the current data if the given filter function  
returns true.</td></tr>
<tr><td valign="top"><a href="#fold-4">fold/4</a></td><td>
  Given a list of optics, performs a recursive fold over the result
  of focusing on the given data structure.</td></tr>
<tr><td valign="top"><a href="#get-2">get/2</a></td><td>
  Given a list of optics, returns a list of the values focused on by
  the final optic.</td></tr>
<tr><td valign="top"><a href="#id-0">id/0</a></td><td>  
Focus on what was given.</td></tr>
<tr><td valign="top"><a href="#is_optic-1">is_optic/1</a></td><td>
  Check if a term is an optic.</td></tr>
<tr><td valign="top"><a href="#lax-1">lax/1</a></td><td>
  Wrap an existing optic to cause it to skip an element when the
  optic would otherwise return <code>{error, undefined}</code> or
  <code>{error, required}</code> during a fold or mapfold operation.</td></tr>
<tr><td valign="top"><a href="#map-3">map/3</a></td><td>
  Given a list of optics, performs a recursive map over the result of
  focusing on the given data structure.</td></tr>
<tr><td valign="top"><a href="#mapfold-4">mapfold/4</a></td><td>
  Given a list of optics, performs a recursive map and fold over the
  result of focusing on the given data structure.</td></tr>
<tr><td valign="top"><a href="#merge-1">merge/1</a></td><td>  
Merge existing optics into a single optic.</td></tr>
<tr><td valign="top"><a href="#new-1">new/1</a></td><td>  
Create a new optic for traversing a data structure.</td></tr>
<tr><td valign="top"><a href="#new-2">new/2</a></td><td>  
Create a new optic for traversing a data structure.</td></tr>
<tr><td valign="top"><a href="#put-3">put/3</a></td><td>
  Given a list of optics, modifies the values focused on by
  the final optic.</td></tr>
<tr><td valign="top"><a href="#require-1">require/1</a></td><td>
  Only focuses on the current data if the given filter function
  returns true.</td></tr>
<tr><td valign="top"><a href="#wrap-2">wrap/2</a></td><td>  
Wrap an existing optic.</td></tr>
<tr><td valign="top"><a href="#wrap-3">wrap/3</a></td><td>  
Wrap an existing optic.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="chain-1">chain/1</a></h3>
<div class="spec">
<p><tt>chain(Optics) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li></ul></p>
<p><tt>Optics</tt>: The list of optics to compose.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Combine existing optics into a chain. In left to right order, each  
optic then focuses on the result of the previous optic. The result  
of this composition is itself an optic.</p>
 
  This is the default composition method used for functions which
  accept optics.</p>

<h3 class="function"><a name="create-3">create/3</a></h3>
<div class="spec">
<p><tt>create(Optic, New, Template) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optic = <a href="#type-optic">optic()</a></tt></li><li><tt>New = <a href="#type-callback_new">callback_new()</a></tt></li><li><tt>Template = term()</tt></li></ul></p>
<p><tt>Optic</tt>: The existing optic to wrap.<br>
<tt>New</tt>: The callback function to apply when the mapfold fails.
  Must take two arguments, the existing data and a template argument
  to use to populate the new data. Should return the new container,
  which will immediately have the wrapped mapfold function re-applied
  after creation.<br>
<tt>Template</tt>: The template value to be given to the callback function.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p>
  Wrap an existing optic to cause it to create a new container when
  the optic would otherwise return <code>{error, undefined}</code> or
  <code>{error, required}</code> during a mapfold operation.</p>

<h3 class="function"><a name="error-1">error/1</a></h3>
<div class="spec">
<p><tt>error(Reason) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Reason = term()</tt></li></ul></p>
<p><tt>Reason</tt>: The error description to return.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Always errors with the given reason.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic:error(reason)], anything).
  {error, reason}</pre></p>

<h3 class="function"><a name="filter-1">filter/1</a></h3>
<div class="spec">
<p><tt>filter(Filter) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Filter = <a href="#type-callback_filter">callback_filter()</a></tt></li></ul></p>
<p><tt>Filter</tt>: The filter function to invoke to determine if the element should be
  focused. Takes the current data as an argument, returns a boolean
  true or false.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Only focuses on the current data if the given filter function  
returns true. Otherwise the data is skipped.</p>
 
  <p>Can fail to be well behaved depending on if the filter criteria is  
part of the focus.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; IsOdd = fun (Elem) -&gt; Elem % 2 == 1 end,
  &gt; optic:get([optic:filter(IsOdd)], [1,2,3]).
  {ok, [1,3]}</pre></p>

<h3 class="function"><a name="fold-4">fold/4</a></h3>
<div class="spec">
<p><tt>fold(Optics, Fold, Acc, Data) -&gt; <a href="#type-option">option</a>(NewAcc)</tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li><li><tt>Fold = <a href="#type-callback_fold">callback_fold()</a></tt></li><li><tt>Acc = term()</tt></li><li><tt>Data = term()</tt></li><li><tt>NewAcc = term()</tt></li></ul></p>
<p><tt>Optics</tt>: A list of optics to apply. Leftmost is applied first.<br>
<tt>Fold</tt>: The callback function to invoke on the focused elements and
  accumulator. Expected to return the modified accumulator.<br>
<tt>Acc</tt>: The initial accumulator value.<br>
<tt>Data</tt>: The container to apply the optics to.<br>
</p>
<p>returns: 
  On success, returns a tuple of ok and the final accumulator value.
  On failure, returns an error tuple.</p>
</div><p>
  Given a list of optics, performs a recursive fold over the result
  of focusing on the given data structure. The order of traversal is
  determined by the optics used.</p>

<h3 class="function"><a name="get-2">get/2</a></h3>
<div class="spec">
<p><tt>get(Optics, Data) -&gt; <a href="#type-option">option</a>(Values)</tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li><li><tt>Data = term()</tt></li><li><tt>Values = [term()]</tt></li></ul></p>
<p><tt>Optics</tt>: A list of optics to apply. Leftmost is applied first.<br>
<tt>Data</tt>: The container to apply the optics to.<br>
</p>
<p>returns: A list of the focused values.</p>
</div><p>
  Given a list of optics, returns a list of the values focused on by
  the final optic.</p>

<h3 class="function"><a name="id-0">id/0</a></h3>
<div class="spec">
<p><tt>id() -&gt; <a href="#type-optic">optic()</a></tt><br></p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on what was given.</p>
 
  <p>This is the identity optic, it can be chained with any other optic  
and will return the same optic.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic:id()], anything).
  {ok,[anything]}</pre></p>

<h3 class="function"><a name="is_optic-1">is_optic/1</a></h3>
<div class="spec">
<p><tt>is_optic(Candidate) -&gt; boolean()</tt>
<ul class="definitions"><li><tt>Candidate = term()</tt></li></ul></p>
<p><tt>Candidate</tt>: The term to test.<br>
</p>
<p>returns: A boolean flag.</p>
</div><p>
  Check if a term is an optic.</p>

<h3 class="function"><a name="lax-1">lax/1</a></h3>
<div class="spec">
<p><tt>lax(Optic) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optic = <a href="#type-optic">optic()</a></tt></li></ul></p>
<p><tt>Optic</tt>: The existing optic to wrap.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p>
  Wrap an existing optic to cause it to skip an element when the
  optic would otherwise return <code>{error, undefined}</code> or
  <code>{error, required}</code> during a fold or mapfold operation.</p>

<h3 class="function"><a name="map-3">map/3</a></h3>
<div class="spec">
<p><tt>map(Optics, Map, Data) -&gt; <a href="#type-option">option</a>(NewData)</tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li><li><tt>Map = <a href="#type-callback_map">callback_map()</a></tt></li><li><tt>Data = term()</tt></li><li><tt>NewData = term()</tt></li></ul></p>
<p><tt>Optics</tt>: A list of optics to apply. Leftmost is applied first.<br>
<tt>Map</tt>: The callback function to invoke on the focused elements. Expected
  to return a modified element.<br>
<tt>Data</tt>: The container to apply the optics to.<br>
</p>
<p>returns: 
  On success, returns a tuple of ok and the modified container.
  On failure, returns an error tuple.</p>
</div><p>
  Given a list of optics, performs a recursive map over the result of
  focusing on the given data structure.</p>

<h3 class="function"><a name="mapfold-4">mapfold/4</a></h3>
<div class="spec">
<p><tt>mapfold(Optics, MapFold, Acc, Data) -&gt; <a href="#type-option">option</a>({NewData, NewAcc})</tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li><li><tt>MapFold = <a href="#type-callback_mapfold">callback_mapfold()</a></tt></li><li><tt>Acc = term()</tt></li><li><tt>Data = term()</tt></li><li><tt>NewData = term()</tt></li><li><tt>NewAcc = term()</tt></li></ul></p>
<p><tt>Optics</tt>: A list of optics to apply. Leftmost is applied first.<br>
<tt>MapFold</tt>: The callback function to invoke on the focused elements and
  accumulator. Expected to return a tuple of the modified element and
  accumulator.<br>
<tt>Acc</tt>: The initial accumulator value.<br>
<tt>Data</tt>: The container to apply the optics to.<br>
</p>
<p>returns: 
  On success, returns a tuple of ok and a tuple of the modified
  container and the final accumulator value. On failure, returns an
  error tuple.</p>
</div><p>
  Given a list of optics, performs a recursive map and fold over the
  result of focusing on the given data structure.</p>

<h3 class="function"><a name="merge-1">merge/1</a></h3>
<div class="spec">
<p><tt>merge(Optics) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li></ul></p>
<p><tt>Optics</tt>: The list of optics to compose.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Merge existing optics into a single optic. In left to right order,  
each optic focuses on the same data. The result of this composition  
is itself an optic.</p>
 
  It is the optic product type.</p>

<h3 class="function"><a name="new-1">new/1</a></h3>
<div class="spec">
<p><tt>new(MapFold) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>MapFold = <a href="#type-optic_mapfold">optic_mapfold()</a></tt></li></ul></p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Create a new optic for traversing a data structure.</p>
 
  This is the less efficient form of optic construction and will
  infer a fold function from the given mapfold function.</p>
<p><b>See also:</b> <a href="#new-2">new/2</a>.</p>

<h3 class="function"><a name="new-2">new/2</a></h3>
<div class="spec">
<p><tt>new(MapFold, Fold) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>MapFold = <a href="#type-optic_mapfold">optic_mapfold()</a></tt></li><li><tt>Fold = <a href="#type-optic_fold">optic_fold()</a></tt></li></ul></p>
<p><tt>MapFold</tt>: At a minimum, an optic requires a mapfold function to be provided
  for both collecting and modifying values. This function must take
  three arguments; a callback function, an initial accumulator value
  and an arbitrary structure to traverse. The callback function will
  expect two values; an element and the current accumulator. It will
  return a two item tuple with the modified element and the modified
  accumulator. The mapfold function will return a two item tuple with
  the modified structure and the final accumulator value, wrapped in
  an ok or error tagged tuple.<br>
<tt>Fold</tt>: A fold function can also be provided for more efficient traversal
  without modification. If one is not provided, it will be
  inefficiently inferred from the MapFold function. This function
  must take three arguments; a callback function, an initial
  accumulator value and an arbitrary structure to traverse. The
  callback function will expect two values; an element and the
  current accumulator. It will return the modified accumulator. The
  fold function will return a final accumulator value, wrapped in an
  ok or error tagged tuple.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Create a new optic for traversing a data structure.</p>
 
  <p>Well behaved optics should implement the following properties:</p>
 
  <ul>
  <li>Get -&gt; Put: Writing the same value as was read should result in
  the original structure.</li>
  <li>Put -&gt; Get: Reading a value that was written should result in
  the same value as was written.</li>
  <li>Put -&gt; Put: Writing a value twice should result in only the
  last written value.</li>
  </ul>
 
  Optics which are not well behaved will be more difficult to use and
  compose with other optics. Their behaviour will change depending on
  the order in which they are applied and the number of times they
  are applied.
 </p>

<h3 class="function"><a name="put-3">put/3</a></h3>
<div class="spec">
<p><tt>put(Optics, Value, Data) -&gt; <a href="#type-option">option</a>(NewData)</tt>
<ul class="definitions"><li><tt>Optics = <a href="#type-optics">optics()</a></tt></li><li><tt>Value = term()</tt></li><li><tt>Data = term()</tt></li><li><tt>NewData = term()</tt></li></ul></p>
<p><tt>Optics</tt>: A list of optics to apply. Leftmost is applied first.<br>
<tt>Data</tt>: The container to apply the optics to.<br>
</p>
<p>returns: 
  On success, returns a tuple of ok and the modified container.
  On failure, returns an error tuple.</p>
</div><p>
  Given a list of optics, modifies the values focused on by
  the final optic.</p>

<h3 class="function"><a name="require-1">require/1</a></h3>
<div class="spec">
<p><tt>require(Filter) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Filter = <a href="#type-callback_filter">callback_filter()</a></tt></li></ul></p>
<p><tt>Filter</tt>: The filter function to invoke to determine if the element should be
  focused. Takes the current data as an argument, returns a boolean
  true or false.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p>
  Only focuses on the current data if the given filter function
  returns true. Otherwise an <code>{error, required}` is returned.
 
  Can fail to be well behaved depending on if the filter criteria is
  part of the focus.
 
  Example:
 
  ```
  &gt; IsOdd = fun (Elem) -&gt; Elem % 2 == 1 end,
  &gt; optic:get([optic:require(IsOdd)], [1,2,3]).
  {error, required}</code>''</p>

<h3 class="function"><a name="wrap-2">wrap/2</a></h3>
<div class="spec">
<p><tt>wrap(Optic, WrapMapFold) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optic = <a href="#type-optic">optic()</a></tt></li><li><tt>WrapMapFold = <a href="#type-optic_wrap_mapfold">optic_wrap_mapfold()</a></tt></li></ul></p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Wrap an existing optic.</p>
 
  This is the less efficient form of optic construction and will
  infer a fold wrapper from the given mapfold wrapper.</p>
<p><b>See also:</b> <a href="#wrap-3">wrap/3</a>.</p>

<h3 class="function"><a name="wrap-3">wrap/3</a></h3>
<div class="spec">
<p><tt>wrap(Optic, WrapMapFold, WrapFold) -&gt; <a href="#type-optic">optic()</a></tt>
<ul class="definitions"><li><tt>Optic = <a href="#type-optic">optic()</a></tt></li><li><tt>WrapMapFold = <a href="#type-optic_wrap_mapfold">optic_wrap_mapfold()</a></tt></li><li><tt>WrapFold = <a href="#type-optic_wrap_fold">optic_wrap_fold()</a></tt></li></ul></p>
<p><tt>Optic</tt>: An existing optic to modify.<br>
<tt>WrapMapFold</tt>: A mapping function to apply to the optic's mapfold function.<br>
<tt>WrapFold</tt>: A mapping function to apply to the optic's fold function.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Wrap an existing optic.</p>
 
  This allows for modifying or replacing the methods of an existing
  optic by applying a mapping function to each of the mapfold and
  fold methods.</p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>test/test_optic_dict.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_dict.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_dict).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(
                     optic:get([optic_dict:all([strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{one, 4}, {two, 4}, {three, 4}])},
                   optic:put([optic_dict:all([strict])],
                             4,
                             dict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, dict:new()},
                 optic:put([optic_dict:all([strict, create])], 4, atom)).

keys_get_test_() ->
    [?_assertEqual({ok, [one, three, two]},
                   sort_get(
                     optic:get([optic_dict:keys([strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:keys([strict])], atom))].

keys_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{four, 1}])},
                   optic:put([optic_dict:keys([strict])],
                             four,
                             dict:from_list([{one, 1}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:keys([strict])], four, atom))].

keys_create_test() ->
    ?assertEqual({ok, dict:new()},
                 optic:put([optic_dict:keys([strict, create])], four, atom)).

values_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(
                     optic:get([optic_dict:values([strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:values([strict])], atom))].

values_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{one, 4}, {two, 4}, {three, 4}])},
                   optic:put([optic_dict:values([strict])],
                             4,
                             dict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:values([strict])], 4, atom))].

values_create_test() ->
    ?assertEqual({ok, dict:new()},
                 optic:put([optic_dict:values([strict, create])], 4, atom)).

associations_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {three, 3}, {two, 2}]},
                   sort_get(
                     optic:get([optic_dict:associations([strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:associations([strict])], atom))].

associations_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{four, 4}])},
                   optic:put([optic_dict:associations([strict])],
                             {four, 4},
                             dict:from_list([{one, 1}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:associations([strict])],
                             {four, 4},
                             atom))].

associations_create_test() ->
    ?assertEqual({ok, dict:new()},
                 optic:put([optic_dict:associations([strict, create])],
                           {four, 4},
                           atom)).

key_get_test_() ->
    [?_assertEqual({ok, [1]},
                   sort_get(
                     optic:get([optic_dict:key(one, [strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:key(one, [strict])], atom))].

key_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{one, 4}, {two, 2}, {three, 3}])},
                   optic:put([optic_dict:key(one, [strict])],
                             4,
                             dict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:key(one, [strict])], 4, atom))].

key_create_test_() ->
    [?_assertEqual({ok, dict:from_list([{one, 1}, {four, 4}])},
                   optic:put([optic_dict:key(four, [strict, create])],
                             4,
                             dict:from_list([{one, 1}]))),
     ?_assertEqual({ok, dict:from_list([{four, 4}])},
                   optic:put([optic_dict:key(four, [strict, create])],
                             4,
                             atom))].

association_get_test_() ->
    [?_assertEqual({ok, [{one, 1}]},
                   sort_get(
                     optic:get([optic_dict:association(one, [strict])],
                               dict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_dict:association(one, [strict])], atom))].

association_put_test_() ->
    [?_assertEqual({ok, dict:from_list([{two, 2}, {three, 3}, {four, 4}])},
                   optic:put([optic_dict:association(one, [strict])],
                             {four, 4},
                             dict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({ok, dict:from_list([{two, 2}, {three, 3}, {four, 4}])},
                   optic:put([optic_dict:association(one, [strict])],
                             {four, 4},
                             dict:from_list([{one, 1}, {two, 2}, {three, 3}, {four, clobbered}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_dict:association(one, [strict])],
                             4,
                             atom))].

association_create_test_() ->
    [?_assertEqual({ok, dict:from_list([{one, 1}, {four, 4}])},
                   optic:put([optic_dict:association(four, [strict, create])],
                             {four, 4},
                             dict:from_list([{one, 1}]))),
     ?_assertEqual({ok, dict:from_list([{four, 4}])},
                   optic:put([optic_dict:association(four, [strict, create])],
                             {four, 4},
                             atom))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

sort_get({ok, Result}) ->
    {ok, lists:sort(Result)}.


<|start_filename|>src/optic_array.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to arrays.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_array).

%% API
-export([all/0,
         all/1,
         nth/1,
         nth/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see all/1
-spec all() -> optic:optic().
all() ->
    all(#{}).

%% @doc
%% Focus on all values of an array.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_array:all()], array:from_list([1,2,3])).
%% {ok,[1,2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    Fold =
    fun (Fun, Acc, Array) ->
            case is_array(Array) of
                true ->
                    {ok, array:foldl(fun (_Index, Elem, InnerAcc) ->
                                             Fun(Elem, InnerAcc)
                                     end, Acc, Array)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Array) ->
            case is_array(Array) of
                true ->
                    {ok, array:foldl(fun (Index, Elem, {InnerArray, InnerAcc}) ->
                                             {NewElem, NewAcc} = Fun(Elem, InnerAcc),
                                             {array:set(Index, NewElem, InnerArray), NewAcc}
                                     end,
                                     {Array, Acc},
                                     Array)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, Template) ->
            array:new([{default, Template}])
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see nth/2
-spec nth(N) -> optic:optic() when
      N :: pos_integer().
nth(N) ->
    nth(N, #{}).

%% @doc
%% Focus on the nth value of an array. Like lists, but unlike the
%% standard array operations, indexing begins at 1.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_array:nth(1)], array:from_list([1,2,3])).
%% {ok,[1]}
%% '''
%% @end
%% @param N The index of the array value to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec nth(N, Options) -> optic:optic() when
      N :: pos_integer(),
      Options :: optic:variations().
nth(N, Options) when N >= 1 ->
    Index = N - 1,
    Fold =
    fun (Fun, Acc, Array) ->
            case is_array(Array) andalso valid_index(Index, Array) of
                true ->
                    Elem = array:get(Index, Array),
                    {ok, Fun(Elem, Acc)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Array) ->
            case is_array(Array) andalso valid_index(Index, Array) of
                true ->
                    Elem = array:get(Index, Array),
                    {NewElem, NewAcc} = Fun(Elem, Acc),
                    {ok, {array:set(Index, NewElem, Array), NewAcc}};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Data, Template) ->
            case is_array(Data) of
                true ->
                    array:resize(N, Data);
                false ->
                    array:new(N, [{default, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

is_array(Unknown) ->
    try array:size(Unknown) of
        _ ->
            true
    catch
        error:badarg ->
            false
    end.

valid_index(Index, Array) when Index >= 0 ->
    case array:is_fix(Array) of
        true ->
            Index < array:size(Array);
        false ->
            true
    end.


<|start_filename|>src/optic_orddict.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to orddicts.
%%%
%%% As orddicts are internally represented as a list of pairs, the
%%% type checks used here are not as reliable as those used for other
%%% optics. Please ensure via other means that these optics are only
%%% used with actual orddicts.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_orddict).

%% API
-export([all/0,
         all/1,
         keys/0,
         keys/1,
         values/0,
         values/1,
         associations/0,
         associations/1,
         key/1,
         key/2,
         association/1,
         association/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see values/1
-spec all() -> optic:optic().
all() ->
    values().

%% @see values/1
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    values(Options).

%% @see keys/1
-spec keys() -> optic:optic().
keys() ->
    keys(#{}).

%% @doc
%% Focus on all keys of an orddict.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_orddict:keys()],
%%             orddict:from_list([{first, 1}, {second, 2}])).
%% {ok,[first,second]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec keys(Options) -> optic:optic() when
      Options :: optic:variations().
keys(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (Key, _Value, InnerAcc) ->
                                              Fun(Key, InnerAcc)
                                      end,
                                      Acc,
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                              {NewKey, NewAcc} = Fun(Key, InnerAcc),
                                              {orddict:store(NewKey, Value, InnerDict), NewAcc}
                                      end,
                                      {orddict:new(), Acc},
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            orddict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see values/1
-spec values() -> optic:optic().
values() ->
    values(#{}).

%% @doc
%% Focus on all values of an orddict.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_orddict:values()],
%%             orddict:from_list([{first, 1}, {second, 2}])).
%% {ok,[1,2]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec values(Options) -> optic:optic() when
      Options :: optic:variations().
values(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (_Key, Value, InnerAcc) ->
                                              Fun(Value, InnerAcc)
                                      end,
                                      Acc,
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                              {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                              {orddict:store(Key, NewValue, InnerDict), NewAcc}
                                      end,
                                      {orddict:new(), Acc},
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            orddict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see associations/1
-spec associations() -> optic:optic().
associations() ->
    associations(#{}).

%% @doc
%% Focus on all associations of an orddict. An association is a tuple
%% of the key and value for each entry.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_orddict:associations()],
%%             orddict:from_list([{first, 1}, {second, 2}])).
%% {ok,[{first,1},{second,2}]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec associations(Options) -> optic:optic() when
      Options :: optic:variations().
associations(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (Key, Value, InnerAcc) ->
                                              Fun({Key, Value}, InnerAcc)
                                      end,
                                      Acc,
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    {ok, orddict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                              {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, InnerAcc),
                                              {orddict:store(NewKey, NewValue, InnerDict), NewAcc}
                                      end,
                                      {orddict:new(), Acc},
                                      Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            orddict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see key/2
-spec key(Key) -> optic:optic() when
      Key :: term().
key(Key) ->
    key(Key, #{}).

%% @doc
%% Focus on the value of an orddict key.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_orddict:key(first)],
%%             orddict:from_list([{first, 1}, {second, 2}])).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec key(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
key(Key, Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    case orddict:find(Key, Dict) of
                        {ok, Value} ->
                            {ok, Fun(Value, Acc)};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    case orddict:find(Key, Dict) of
                        {ok, Value} ->
                            {NewValue, NewAcc} = Fun(Value, Acc),
                            {ok, {orddict:store(Key, NewValue, Dict), NewAcc}};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Dict, Template) ->
            case is_orddict(Dict) of
                true ->
                    orddict:store(Key, Template, Dict);
                false ->
                    orddict:from_list([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see association/2
-spec association(Key) -> optic:optic() when
      Key :: term().
association(Key) ->
    association(Key, #{}).

%% @doc
%% Focus on the association for an orddict key. An association is the
%% tuple of a orddict key and value. If the key is modified, the optic is
%% no longer well behaved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_orddict:association(first)],
%%             orddict:from_list([{first, 1}, {second, 2}])).
%% {ok,[{first,1}]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec association(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
association(Key, Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    case orddict:find(Key, Dict) of
                        {ok, Value} ->
                            {ok, Fun({Key, Value}, Acc)};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_orddict(Dict) of
                true ->
                    case orddict:find(Key, Dict) of
                        {ok, Value} ->
                            {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, Acc),
                            {ok, {orddict:store(NewKey, NewValue, orddict:erase(Key, Dict)), NewAcc}};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Dict, Template) ->
            case is_orddict(Dict) of
                true ->
                    orddict:store(Key, Template, Dict);
                false ->
                    orddict:from_list([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

%% @private
%% @doc
%% Check if a container is likely to be an orddict. Is both unable to
%% disambiguate an empty list and an empty orddict, as well as only
%% inspecting the first element of the orddict in order to keep the
%% check constant time.
%% @end
is_orddict([]) ->
    true;
is_orddict([{_, _} | _]) ->
    true;
is_orddict(_) ->
    false.


<|start_filename|>test/test_optic_path.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_path.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_path).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

new_test_() ->
    [?_assertEqual({ok, [value]},
                   optic:get(optic_path:new(["key"]), #{"key"=>value})),
     ?_assertEqual({ok, [value]},
                   optic:get(optic_path:new(["key"]), [{"key", value}])),
     ?_assertEqual({ok, [value]},
                   optic:get(optic_path:new([<<"key">>]),
                             #{<<"key">>=>value})),
     ?_assertEqual({ok, [value]},
                   optic:get(optic_path:new([1]), [value])),
     ?_assertEqual({ok, [1, 2, 3]},
                   optic:get(optic_path:new(['*']), [1, 2, 3])),
     ?_assertEqual({ok, [value]},
                   optic:get(optic_path:new(["key", 1, "nested"]),
                             #{"key"=>[#{"nested"=>value}]}))].


<|start_filename|>doc/overview-summary.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>optic
</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<h1>optic
</h1>
<p>Copyright © 2019 <NAME>
</p>
<p><b>Version:</b> 3.1.0
</p>
<p><b>Authors:</b> <NAME> (<a href="mailto:<EMAIL>"><tt><EMAIL></tt></a>).</p>
<h3>Overview</h3>

<p><img src="lenses.jpg" alt="Camera Lenses"></p>

<p>This is an Erlang/OTP library for retrieving and modifying nested values, in
the spirit of <a href="https://en.wikibooks.org/wiki/Haskell/Lenses_and_functional_references">Haskell's
lens library</a>. Functional selectors for deeply nested values are
constructed by composing "optics", each of which specifies a way to focus on a
particular kind of value.</p>

<p>For example, say we had a list of deserialized JSON entities representing pets
for sale that we wanted to modify.</p>

<pre>   
&gt; Pets = [#{
    &lt;&lt;"id"&gt;&gt; =&gt; 628178654,
    &lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"spot"&gt;&gt;,
    &lt;&lt;"status"&gt;&gt; =&gt; &lt;&lt;"available"&gt;&gt;,
    &lt;&lt;"category"&gt;&gt; =&gt; #{
      &lt;&lt;"id"&gt;&gt; =&gt; 3216199393,
      &lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"dog"&gt;&gt;
    }}].
</pre>

<p>We could then update all pets to a new status by:</p>

<pre>   
&gt; optic:put([optic_lists:all(), optic_maps:key(&lt;&lt;"status"&gt;&gt;)],
            &lt;&lt;"sold"&gt;&gt;,
            Pets).

{ok,[#{
  &lt;&lt;"id"&gt;&gt; =&gt; 628178654,
  &lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"spot"&gt;&gt;,
  &lt;&lt;"status"&gt;&gt; =&gt; &lt;&lt;"sold"&gt;&gt;,
  &lt;&lt;"category"&gt;&gt; =&gt; #{
    &lt;&lt;"id"&gt;&gt; =&gt; 3216199393,
    &lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"dog"&gt;&gt;
  }}]}
</pre>

<h3>Getting Started</h3>

<p>This library is published to <a href="https://hex.pm">hex.pm</a> as <a href="https://hex.pm/packages/optic">optic</a>. If you're using <a href="https://www.rebar3.org/">rebar3</a> as your build tool, it can be added
as a dependency to your rebar.config as follows:</p>

<pre>   
{deps, [{optic}]}.
</pre>

<h3>Usage</h3>

<p>The library is expected to be used by first creating a list of <em>optics</em>
which represent operations on particular kinds of containers. That list can
then be used with a variety of different common operations in the library for
reading or writing values in nested data structures in different ways.</p>

<h4>Optics</h4>

<p>An optic is an opaque record that represents a method for traversing over and
modifying a particular container. Many different pre-created optics are
included in the library for standard Erlang containers as well as a method for
creating new optics.</p>

<p>The fundamental operation supported is a mapfold, though a fold may also be
implemented (and is recommended) for better performance for read only
operations. Additionally convenience functions for getting and putting static
values are included and are built on top of the more powerful primitive
operations.</p>

<p>Optics which are associative and idempotent are well behaved. These optics
can be easily combined in any way this library supports without surprise.
Optics which do not have these properties will require careful attention to
how they are used and the order in which they are applied.</p>

<p>Most of the optics in this library support the same set of options to control
how they behave. Those options are passed as a map or proplist:</p>

<ul>
<li>strict: When true, this causes an error to be returned when asked to
traverse over a container of an unexpected type. When false or not given,
unexpected containers will be silently skipped.</li>
<li>create: When given, this causes a new container of the expected type to be
created when no container is present or when asked to traverse over a
container of an unexpected type. If the traversal type needs to create
individual values, the value of the create property is used as a template to
create the needed default objects. Setting this property causes the optic to
no longer be well behaved.</li>
<li>filter: When given, expects a value as a filter function to determine if
the traversed element should be focused or skipped. The filter function should
take a single arbitrary element and return a boolean true/false. If the
criteria the filter function uses to select an element is modified, the
filtered optic will no longer be well behaved.</li>
<li>require: When given, expects a value as a filter function to determine if
the traversed element can be focused. If the element can not be focused,
<code>{error, required}</code> will be returned. The filter function should take a single
arbitrary element and return a boolean true/false. If the criteria the filter
function uses to select an element is modified, the filtered optic will no
longer be well behaved.</li>
</ul>

<h4>Examples</h4>

<p>Let's look at some examples of how optics can be used. We'll start with a list
of maps and demonstrate various ways it can be processed:</p>

<pre>   
&gt; Data = [#{name =&gt; first}, #{name =&gt; second}, #{name =&gt; third}].
</pre>

<p>We can extract a single value using one of the lists optics to extract the
head of the list, and combine it with an optic to extract the value of the
"name" key from the map.</p>

<pre>   
&gt; optic:get([optic_lists:head(), optic_maps:key(name)], Data).
{ok,[first]}
</pre>

<p>This shows how optics are applied from left to right.</p>

<p>If we wanted to traverse over all the members of the list, we can simply
change the list optic used to do so:</p>

<pre>   
&gt; optic:get([optic_lists:all(), optic_maps:key(name)], Data).
{ok,[first,second,third]}
</pre>

<p>Optics can also be composed using <code>optic:merge/1</code> in order to select
multiple elements at the same level. For instance, to select only the first
two maps, we could do this:</p>

<pre>   
&gt; FirstTwo = optic:merge([optic_lists:nth(1), optic_lists:nth(2)]),
&gt; optic:get([FirstTwo, optic_maps:key(name)], Data).
{ok,[first,second]}
</pre>

<p>When presented with heterogeneous data, the strict option can be used to
control how it is handled. We may want to simply skip it when such is
expected, or we may want to report an error. The only change is in how the
optic is constructed:</p>

<pre>   
&gt; Data = [#{name =&gt; first},#{id =&gt; second},#{name =&gt; third}].
[#{name =&gt; first},#{id =&gt; second},#{name =&gt; third}].

&gt; optic:get([optic_lists:all(), optic_maps:key(name)], Data).
{ok,[first,third]}

&gt; optic:get([optic_lists:all(), optic_maps:key(name, #{strict =&gt; false})], Data).
{ok,[first,third]} 

&gt; optic:get([optic_lists:all(), optic_maps:key(name, #{strict =&gt; true})], Data).
{error,undefined}
</pre>

<p>We can see here that we get lax handling of unexpected types by default.</p>

<p>The create option is closely related, as creation is only triggered in cases
where strict would have returned an error. In that case a container is instead
either constructed or modified to allow the operation to succeed.</p>

<p>Let's explore the variations in the context of modifying a container:</p>

<pre>   
&gt; Data = [#{name =&gt; first},#{id =&gt; second},#{name =&gt; third}].
[#{name =&gt; first},#{id =&gt; second},#{name =&gt; third}].

&gt; optic:put([optic_lists:all(), optic_maps:key(name)], example, Data).
{ok,[#{name =&gt; example},#{id =&gt; second},#{name =&gt; example}]}

&gt; optic:put([optic_lists:all(), optic_maps:key(name, #{strict=&gt;true})], example, Data).
{error,undefined}

&gt; optic:put([optic_lists:all(), optic_maps:key(name, #{create=&gt;undefined})], example, Data).
{ok,[#{name =&gt; example},
     #{id =&gt; second,name =&gt; example},
     #{name =&gt; example}]}
</pre>

<p>The value given to create is used as a template when additional values need to
be constructed. In many cases, it is then immediately overwritten. The
behaviour is context dependent and will vary based on the optic used.</p>

<pre>   
&gt; optic:put([optic_lists:nth(3, #{create =&gt; #{}}),
             optic_maps:key(name, #{create =&gt; undefined})], example, []).
{ok,[#{},#{},#{name =&gt; example}]}
</pre>

<p>Finally, the filter option can be used to control which elements are selected
based on their values. For instance, say we wanted to only collect the status
of maps with a particular name:</p>

<pre>   
&gt; Data = [#{name =&gt; first, status =&gt; ready},
          #{name =&gt; second, status =&gt; ready},
          #{name =&gt; third, status =&gt; delayed}].
[#{name =&gt; first,status =&gt; ready},
 #{name =&gt; second,status =&gt; ready},
 #{name =&gt; third,status =&gt; delayed}]
&gt; optic:get([optic_lists:all([{filter,
                               fun (Elem) -&gt; maps:get(name, Elem) == third end}]),
             optic_maps:key(status)], Data).
{ok,[delayed]}
</pre>

<p>More complicated operations should refer to the <code>fold/4</code>, <code>map/3</code> and
<code>mapfold/4</code> functions to allow for values to be computed during the traversal.</p>

<h4>Paths</h4>

<p>For traversing JSON like structures of maps and lists, the <code>optic_path</code> module
provides a simplified interface for optic construction. It parses a list of
path components into a list of optics that can be used with any of the optic
traversal functions.</p>

<pre>   
&gt; Data = #{&lt;&lt;"result"&gt;&gt; =&gt; [#{&lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"example"&gt;&gt;}]},
&gt; Path = optic_path:new([&lt;&lt;"result"&gt;&gt;, '*', &lt;&lt;"name"&gt;&gt;]),
&gt; optic:get(Path, Data).
{ok,[&lt;&lt;"example"&gt;&gt;]}

&gt; optic:put(Path, &lt;&lt;"modified"&gt;&gt;, Data).
{ok,#{&lt;&lt;"result"&gt;&gt; =&gt; [#{&lt;&lt;"name"&gt;&gt; =&gt; &lt;&lt;"modified"&gt;&gt;}]}}
</pre>

<p>Notable restrictions for paths are they require map-like keys to be strings or
binaries. No provision is made for the usual optic options, instead traversal
is always lax and creation is never done.</p>

<h3>Contributing</h3>

<p>Please fork the repo and submit a PR. Tests are run via:</p>

<pre>   
rebar3 eunit
</pre>

<p>Documentation is autogenerated using edown and edoc via:</p>

<pre>   
rebar3 as markdown edoc
</pre>

<p>The application has only been tested with Erlang/OTP 21 on Windows 10. Reports
of success (or failure!) on other versions and operating systems are
appreciated.</p>

<h3>Lineage</h3>

<p>I first encountered lenses via the
<a href="https://github.com/fogfish/datum">Datum</a> library, but wanted a
version that supported traversables instead of lenses in order to support
updating multiple elements. As lenses are a special case of traversables, this
allowed for using a single type to represent both.</p>

<p>There are a number of good introductions to lenses,
<a href="https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial">this</a>
was the most accessible for me.</p>

<p>This library was initially conceived with the intention of making it easy to
modify deeply nested JSON, so JSON related data structures were the first
implemented.</p>

<h3>Attribution</h3>

Image by <NAME>
CC BY-SA 3.0 <a href="https://creativecommons.org/licenses/by/3.0/deed.en" target="_top"><tt>https://creativecommons.org/licenses/by/3.0/deed.en</tt></a>

<hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>src/optic_tuples.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to tuples.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_tuples).

%% API
-export([all/0,
         all/1,
         element/1,
         element/2,
         field/3,
         field/4]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see all/1
-spec all() -> optic:optic().
all() ->
    all(#{}).

%% @doc
%% Focus on all elements of a tuple.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_tuples:all()], {1,2,3}).
%% {ok,[1,2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    Fold =
    fun (Fun, Acc, Tuple) when is_tuple(Tuple) ->
            {ok, lists:foldl(Fun, Acc, tuple_to_list(Tuple))};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Tuple) when is_tuple(Tuple) ->
            {NewList, NewAcc} = lists:mapfoldl(Fun, Acc, tuple_to_list(Tuple)),
            {ok, {list_to_tuple(NewList), NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            {}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see element/2
-spec element(N) -> optic:optic() when
      N :: pos_integer().
element(N) ->
    optic_tuples:element(N, #{}).

%% @doc
%% Focus on the nth element of a tuple. As with `erlang:element/2',
%% indexing begins at 1.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_tuples:element(1)], {1,2,3}).
%% {ok,[1]}
%% '''
%% @end
%% @param N The index of the tuple element to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec element(N, Options) -> optic:optic() when
      N :: pos_integer(),
      Options :: optic:variations().
element(N, Options) ->
    Fold =
    fun (Fun, Acc, Tuple) when N =< tuple_size(Tuple) ->
            Nth = erlang:element(N, Tuple),
            {ok, Fun(Nth, Acc)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Tuple) when N =< tuple_size(Tuple) ->
            Nth = erlang:element(N, Tuple),
            {NewNth, NewAcc} = Fun(Nth, Acc),
            {ok, {erlang:setelement(N, Tuple, NewNth), NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (Tuple, Template) when is_tuple(Tuple) ->
            list_to_tuple(tuple_to_list(Tuple) ++
                          lists:duplicate(N - tuple_size(Tuple), Template));
        (_Data, Template) ->
            list_to_tuple(lists:duplicate(N, Template))
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see field/4
-spec field(Tag, Size, N) -> optic:optic() when
      Tag :: atom(),
      Size :: pos_integer(),
      N :: pos_integer().
field(Tag, Size, N) ->
    field(Tag, Size, N, #{}).

%% @doc
%% Focus on a record field. As records are a compiler construct, this
%% depends on the `?OPTIC_FIELD' macro in `include/optic_tuples.hrl'
%% to construct the required arguments from the record definition.
%%
%% Given the record definition:
%%
%% ```
%% -include_lib("optic/include/optic_tuples.hrl").
%% -record(example, {first}).
%% '''
%%
%% Example:
%%
%% ```
%% > optic:get([optic_tuples:field(?OPTIC_FIELD(example, first))],
%%             #example{first=1}).
%% {ok,[1]}
%% '''
%% @end
%% @param Tag The expected record tag.
%% @param Size The expected record size.
%% @param N The index of the field in the record tuple.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec field(Tag, Size, N, Options) -> optic:optic() when
      Tag :: atom(),
      Size :: pos_integer(),
      N :: pos_integer(),
      Options :: optic:variations().
field(Tag, Size, N, Options) ->
    Fold =
    fun (Fun, Acc, Tuple) when erlang:element(1, Tuple) == Tag,
                               Size == tuple_size(Tuple),
                               N > 1,
                               N =< tuple_size(Tuple) ->
            Nth = erlang:element(N, Tuple),
            {ok, Fun(Nth, Acc)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Tuple) when erlang:element(1, Tuple) == Tag,
                               Size == tuple_size(Tuple),
                               N > 1,
                               N =< tuple_size(Tuple) ->
            Nth = erlang:element(N, Tuple),
            {NewNth, NewAcc} = Fun(Nth, Acc),
            {ok, {erlang:setelement(N, Tuple, NewNth), NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (_Data, Template) ->
            list_to_tuple([Tag] ++ lists:duplicate(Size - 1, Template))
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).


<|start_filename|>doc/optic_generic.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_generic</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_generic</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>   
A set of generic optics that can be applied to multiple container   
types.


<h2><a name="description">Description</a></h2><p>   
A set of generic optics that can be applied to multiple container   
types.</p>
  
   <p>Intended both as a convenience and to support optic creation from   
parsed paths in optic_path.</p>
  
   Because of the ambiguous types they support, these optics do not
   support the standard optic options. Instead, they always skip
   unexpected types and never create missing values.
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#index-1">index/1</a></td><td>  
Focus on an element of a list like container.</td></tr>
<tr><td valign="top"><a href="#key-1">key/1</a></td><td>  
Focus on the value of many different key/value like mappings.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="index-1">index/1</a></h3>
<div class="spec">
<p><tt>index(Index) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Index = non_neg_integer()</tt></li></ul></p>
<p><tt>Index</tt>: The one based index of the element to focus on.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on an element of a list like container. Indexing begins at 1.  
Understands how to focus on lists and tuples. Does not support the  
usual optic options.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_generic:index(3)], [1, 2, 3]).
  {ok,[3]}</pre></p>

<h3 class="function"><a name="key-1">key/1</a></h3>
<div class="spec">
<p><tt>key(Key) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Key = term()</tt></li></ul></p>
<p><tt>Key</tt>: The key to focus on.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the value of many different key/value like mappings.  
Understands how to focus on maps, property lists, dicts, orddicts  
and gb_trees. Does not support the usual optic options.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_generic:key(first)], #{first =&gt; 1}).
  {ok,[1]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>test/test_optic_lists.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_lists.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_lists).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   optic:get([optic_lists:all([strict])], [1, 2, 3])),
     ?_assertEqual({ok, [3]},
                   optic:get([optic_lists:all([strict,
                                               {filter,
                                                fun (Elem) -> Elem == 3 end}])],
                             [1, 2, 3])),
     ?_assertEqual({ok, [3]},
                   optic:get([optic_lists:all([strict,
                                               {require,
                                                fun (Elem) -> Elem == 3 end}])],
                             [3])),
     ?_assertEqual({error, required},
                   optic:get([optic_lists:all([create,
                                               {require,
                                                fun (Elem) -> Elem == 3 end}])],
                             [1, 2, 3])),
     ?_assertEqual({ok, []},
                   optic:get([optic_lists:all()], atom)),
     ?_assertEqual({ok, []},
                   optic:get([optic_lists:all([{strict, false}])], atom)),
     ?_assertEqual({error, undefined},
                   optic:get([optic_lists:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, [4, 4, 4]},
                   optic:put([optic_lists:all([strict])], 4, [1, 2, 3])),
     ?_assertEqual({ok, [1, 2, 4]},
                   optic:put([optic_lists:all([strict,
                                               {filter,
                                                fun (Elem) -> Elem == 3 end}])],
                             4,
                             [1, 2, 3])),
     ?_assertEqual({ok, [4]},
                   optic:put([optic_lists:all([strict,
                                               {require,
                                                fun (Elem) -> Elem == 3 end}])],
                             4,
                             [3])),
     ?_assertEqual({error, required},
                   optic:put([optic_lists:all([create,
                                               {require,
                                                fun (Elem) -> Elem == 3 end}])],
                             4,
                             [1, 2, 3])),
     ?_assertEqual({ok, atom},
                   optic:put([optic_lists:all()], 4, atom)),
     ?_assertEqual({ok, atom},
                   optic:put([optic_lists:all([{strict, false}])], 4, atom)),
     ?_assertEqual({error, undefined},
                   optic:put([optic_lists:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, []},
                 optic:put([optic_lists:all([strict, create])], 4, atom)).

head_get_test_() ->
    [?_assertEqual({ok, [1]},
                   optic:get([optic_lists:head([strict])], [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_lists:head([strict])], atom))].

head_put_test_() ->
    [?_assertEqual({ok, [4, 2, 3]},
                   optic:put([optic_lists:head([strict])], 4, [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_lists:head([strict])], 4, atom))].

head_create_test() ->
    ?assertEqual({ok, [4]},
                 optic:put([optic_lists:head([strict, create])], 4, atom)).

tail_get_test_() ->
    [?_assertEqual({ok, [2, 3]},
                   optic:get([optic_lists:tail([strict])], [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_lists:tail([strict])], atom))].

tail_put_test_() ->
    [?_assertEqual({ok, [1, 4, 4]},
                   optic:put([optic_lists:tail([strict])], 4, [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_lists:tail([strict])], 4, atom))].

tail_create_test() ->
    ?assertEqual({ok, [undefined]},
                 optic:put([optic_lists:tail([strict,
                                              {create, undefined}])],
                           4,
                           atom)).

nth_get_test_() ->
    [?_assertEqual({ok, [1]},
                   optic:get([optic_lists:nth(1, [strict])], [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:get([optic_lists:nth(1, [strict])], atom))].

nth_put_test_() ->
    [?_assertEqual({ok, [4, 2, 3]},
                   optic:put([optic_lists:nth(1, [strict])], 4, [1, 2, 3])),
     ?_assertEqual({error, undefined},
                   optic:put([optic_lists:nth(1, [strict])], 4, atom))].

nth_create_test_() ->
    [?_assertEqual({ok, [undefined, 4]},
                   optic:put([optic_lists:nth(2, [strict,
                                                  {create, undefined}])],
                             4,
                             atom)),
     ?_assertEqual({ok, [1, undefined, 4]},
                   optic:put([optic_lists:nth(3, [strict,
                                                  {create, undefined}])],
                             4,
                             [1]))].


<|start_filename|>src/optic_lists.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to lists.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_lists).

%% API
-export([all/0,
         all/1,
         head/0,
         head/1,
         tail/0,
         tail/1,
         nth/1,
         nth/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see all/1
-spec all() -> optic:optic().
all() ->
    all(#{}).

%% @doc
%% Focus on all elements of a list.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_lists:all()], [1,2,3]).
%% {ok,[1,2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            {ok, lists:foldl(Fun, Acc, List)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            {ok, lists:mapfoldl(Fun, Acc, List)};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            []
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see head/1
-spec head() -> optic:optic().
head() ->
    head(#{}).

%% @doc
%% Focus on the head of a list. The list must have at least one
%% element to have a head.
%% 
%% Example:
%%
%% ```
%% > optic:get([optic_lists:head()], [1,2,3]).
%% {ok,[1]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec head(Options) -> optic:optic() when
      Options :: optic:variations().
head(Options) ->
    Fold =
    fun (Fun, Acc, [Head | _]) ->
            {ok, Fun(Head, Acc)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, [Head | Tail]) ->
            {NewHead, NewAcc} = Fun(Head, Acc),
            {ok, {[NewHead | Tail], NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (_Data, Template) ->
            [Template]
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see tail/1
-spec tail() -> optic:optic().
tail() ->
    tail(#{}).

%% @doc
%% Focus on the tail of a list. A list must have at least one element
%% to have a tail.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_lists:tail()], [1,2,3]).
%% {ok,[2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec tail(Options) -> optic:optic() when
      Options :: optic:variations().
tail(Options) ->
    Fold =
    fun (Fun, Acc, [_ | Tail]) ->
            {ok, lists:foldl(Fun, Acc, Tail)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, [Head | Tail]) ->
            {NewTail, NewAcc} = lists:mapfoldl(Fun, Acc, Tail),
            {ok, {[Head | NewTail], NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (_Data, Template) ->
            [Template]
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see nth/2
-spec nth(N) -> optic:optic() when
      N :: pos_integer().
nth(N) ->
    nth(N, #{}).

%% @doc
%% Focus on the nth element of a list. As with `lists:nth/2', indexing
%% begins at 1.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_lists:nth(1)], [1,2,3]).
%% {ok,[1]}
%% '''
%% @end
%% @param N The index of the list element to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec nth(N, Options) -> optic:optic() when
      N :: pos_integer(),
      Options :: optic:variations().
nth(N, Options) ->
    Fold =
    fun (Fun, Acc, List) when N =< length(List) ->
            Nth = lists:nth(N, List),
            {ok, Fun(Nth, Acc)};
        (_, _, _) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when N =< length(List) ->
            {Before, [Head | Tail]} = lists:split(N - 1, List),
            {NewHead, NewAcc} = Fun(Head, Acc),
            {ok, {Before ++ [NewHead] ++ Tail, NewAcc}};
        (_, _, _) ->
            {error, undefined}
    end,
    New =
    fun (Data, Template) when is_list(Data) ->
            Data ++ lists:duplicate(N - length(Data), Template);
        (_Data, Template) ->
            lists:duplicate(N, Template)
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).


<|start_filename|>src/optic_gb_trees.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to gb_trees.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_gb_trees).

%% API
-export([all/0,
         all/1,
         keys/0,
         keys/1,
         values/0,
         values/1,
         associations/0,
         associations/1,
         key/1,
         key/2,
         association/1,
         association/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see values/1
-spec all() -> optic:optic().
all() ->
    values().

%% @see values/1
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    values(Options).

%% @see keys/1
-spec keys() -> optic:optic().
keys() ->
    keys(#{}).

%% @doc
%% Focus on all keys of a gb_tree.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_gb_trees:keys()],
%%             gb_trees:from_orddict([{first, 1}, {second, 2}])).
%% {ok,[first,second]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec keys(Options) -> optic:optic() when
      Options :: optic:variations().
keys(Options) ->
    Fold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (Key, _Value, InnerAcc) ->
                                      Fun(Key, InnerAcc)
                              end,
                              Acc,
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (Key, Value, {InnerTree, InnerAcc}) ->
                                      {NewKey, NewAcc} = Fun(Key, InnerAcc),
                                      {gb_trees:enter(NewKey, Value, InnerTree), NewAcc}
                              end,
                              {gb_trees:empty(), Acc},
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            gb_trees:empty()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see values/1
-spec values() -> optic:optic().
values() ->
    values(#{}).

%% @doc
%% Focus on all values of a gb_tree.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_gb_trees:values()],
%%             gb_trees:from_orddict([{first, 1}, {second, 2}])).
%% {ok,[1,2]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec values(Options) -> optic:optic() when
      Options :: optic:variations().
values(Options) ->
    Fold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (_Key, Value, InnerAcc) ->
                                      Fun(Value, InnerAcc)
                              end,
                              Acc,
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (Key, Value, {InnerTree, InnerAcc}) ->
                                      {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                      {gb_trees:enter(Key, NewValue, InnerTree), NewAcc}
                              end,
                              {gb_trees:empty(), Acc},
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            gb_trees:empty()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see associations/1
-spec associations() -> optic:optic().
associations() ->
    associations(#{}).

%% @doc
%% Focus on all associations of a gb_tree. An association is a tuple of
%% the key and value for each entry.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_gb_trees:associations()],
%%             gb_trees:from_orddict([{first, 1}, {second, 2}])).
%% {ok,[{first,1},{second,2}]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec associations(Options) -> optic:optic() when
      Options :: optic:variations().
associations(Options) ->
    Fold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (Key, Value, InnerAcc) ->
                                      Fun({Key, Value}, InnerAcc)
                              end,
                              Acc,
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    {ok, fold(fun (Key, Value, {InnerTree, InnerAcc}) ->
                                      {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, InnerAcc),
                                      {gb_trees:enter(NewKey, NewValue, InnerTree), NewAcc}
                              end,
                              {gb_trees:empty(), Acc},
                              Tree)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            gb_trees:empty()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see key/2
-spec key(Key) -> optic:optic() when
      Key :: term().
key(Key) ->
    key(Key, #{}).

%% @doc
%% Focus on the value of a gb_tree key.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_gb_trees:key(first)],
%%             gb_trees:from_orddict([{first, 1}, {second, 2}])).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec key(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
key(Key, Options) ->
    Fold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {ok, Fun(Value, Acc)};
                        none ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {NewValue, NewAcc} = Fun(Value, Acc),
                            {ok, {gb_trees:enter(Key, NewValue, Tree), NewAcc}};
                        none ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Tree, Template) ->
            case is_gb_tree(Tree) of
                true ->
                    gb_trees:enter(Key, Template, Tree);
                false ->
                    gb_trees:from_orddict([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see association/2
-spec association(Key) -> optic:optic() when
      Key :: term().
association(Key) ->
    association(Key, #{}).

%% @doc
%% Focus on the association for a gb_tree key. An association is the
%% tuple of a gb_tree key and value. If the key is modified, the optic is
%% no longer well behaved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_gb_trees:association(first)],
%%             gb_trees:from_orddict([{first, 1}, {second, 2}])).
%% {ok,[{first,1}]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec association(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
association(Key, Options) ->
    Fold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {ok, Fun({Key, Value}, Acc)};
                        none ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Tree) ->
            case is_gb_tree(Tree) of
                true ->
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, Acc),
                            {ok, {gb_trees:enter(NewKey, NewValue, gb_trees:delete(Key, Tree)), NewAcc}};
                        none ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Tree, Template) ->
            case is_gb_tree(Tree) of
                true ->
                    gb_trees:enter(Key, Template, Tree);
                false ->
                    gb_trees:from_orddict([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

fold(Fun, Acc0, Tree) ->
    List = gb_trees:to_list(Tree),
    lists:foldl(fun ({Key, Value}, Acc) ->
                        Fun(Key, Value, Acc)
                end,
                Acc0,
                List).

is_gb_tree(Unknown) ->
    try gb_trees:size(Unknown) of
        _ ->
            true
    catch
        error:function_clause ->
            false
    end.


<|start_filename|>doc/optic_path.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_path</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_path</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#types">Data Types</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
   Utility functions for constructing optics from lists of selectors.


<h2><a name="description">Description</a></h2>
   Utility functions for constructing optics from lists of selectors.
<h2><a name="types">Data Types</a></h2>

<h3 class="typedecl"><a name="type-path">path()</a></h3>
<p><tt>path() = string() | binary() | non_neg_integer() | '*'</tt></p>
<p>  A single path component.</p>

<h3 class="typedecl"><a name="type-paths">paths()</a></h3>
<p><tt>paths() = [<a href="#type-path">path()</a>]</tt></p>
<p>  A list of path components.</p>

<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#new-1">new/1</a></td><td>  
Construct a list of optics from a path.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="new-1">new/1</a></h3>
<div class="spec">
<p><tt>new(Paths) -&gt; <a href="optic.html#type-optics">optic:optics()</a></tt>
<ul class="definitions"><li><tt>Paths = <a href="#type-paths">paths()</a></tt></li></ul></p>
<p><tt>Paths</tt>: A list of path components to convert.<br>
</p>
<p>returns: A list of opaque optic records.</p>
</div><p><p>  
Construct a list of optics from a path. The type of the path  
component determines the optic used:</p>
 
  <ul>
  <li>string: A key for a map-like structure.</li>
  <li>binary: A key for a map-like structure.</li>
  <li>integer: An index into a list-like structure.</li>
  <li>'*': All elements of a list.</li>
  </ul>
 
  <p>This heavily depends on the <code>optic_generic</code> module, see the optics  
there for the full list of containers supported.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get(optic_path(["first"]), ${"first" =&gt; 1, "second" =&gt; 2}).
  {ok,[1]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>src/optic_maps.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to maps.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_maps).

%% API
-export([all/0,
         all/1,
         keys/0,
         keys/1,
         values/0,
         values/1,
         associations/0,
         associations/1,
         key/1,
         key/2,
         association/1,
         association/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see values/1
-spec all() -> optic:optic().
all() ->
    values().

%% @see values/1
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    values(Options).

%% @see keys/1
-spec keys() -> optic:optic().
keys() ->
    keys(#{}).

%% @doc
%% Focus on all keys of a map.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_maps:keys()], #{first => 1, second => 2}).
%% {ok,[first,second]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec keys(Options) -> optic:optic() when
      Options :: optic:variations().
keys(Options) ->
    Fold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (Key, _Value, InnerAcc) ->
                                   Fun(Key, InnerAcc)
                           end,
                           Acc,
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (Key, Value, {InnerMap, InnerAcc}) ->
                                   {NewKey, NewAcc} = Fun(Key, InnerAcc),
                                   {InnerMap#{NewKey=>Value}, NewAcc}
                           end,
                           {#{}, Acc},
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            #{}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see values/1
-spec values() -> optic:optic().
values() ->
    values(#{}).

%% @doc
%% Focus on all values of a map.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_maps:values()], #{first => 1, second => 2}).
%% {ok,[1,2]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec values(Options) -> optic:optic() when
      Options :: optic:variations().
values(Options) ->
    Fold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (_Key, Value, InnerAcc) ->
                                   Fun(Value, InnerAcc)
                           end,
                           Acc,
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (Key, Value, {InnerMap, InnerAcc}) ->
                                   {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                   {InnerMap#{Key=>NewValue}, NewAcc}
                           end,
                           {#{}, Acc},
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            #{}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see associations/1
-spec associations() -> optic:optic().
associations() ->
    associations(#{}).

%% @doc
%% Focus on all associations of a map. An association is a tuple of
%% the key and value for each entry.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_maps:associations()], #{first => 1, second => 2}).
%% {ok,[{first,1},{second,2}]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec associations(Options) -> optic:optic() when
      Options :: optic:variations().
associations(Options) ->
    Fold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (Key, Value, InnerAcc) ->
                                   Fun({Key, Value}, InnerAcc)
                           end,
                           Acc,
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            {ok, maps:fold(fun (Key, Value, {InnerMap, InnerAcc}) ->
                                   {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, InnerAcc),
                                   {InnerMap#{NewKey=>NewValue}, NewAcc}
                           end,
                           {#{}, Acc},
                           Map)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            #{}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see key/2
-spec key(Key) -> optic:optic() when
    Key :: term().
key(Key) ->
    key(Key, #{}).

%% @doc
%% Focus on the value of a map key.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_maps:key(first)], #{first => 1, second => 2}).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec key(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
key(Key, Options) ->
    Fold =
    fun (Fun, Acc, #{Key:=Value}) ->
            {ok, Fun(Value, Acc)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, #{Key:=Value} = Map) when is_map(Map) ->
            {NewValue, NewAcc} = Fun(Value, Acc),
            {ok, {Map#{Key:=NewValue}, NewAcc}};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (Map, Template) when is_map(Map) ->
            Map#{Key=>Template};
        (_Data, Template) ->
            #{Key=>Template}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see association/2
-spec association(Key) -> optic:optic() when
      Key :: term().
association(Key) ->
    association(Key, #{}).

%% @doc
%% Focus on the association for a map key. An association is the tuple
%% of a map key and value. If the key is modified, the optic is no
%% longer well behaved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_maps:association(first)], #{first => 1, second => 2}).
%% {ok,[{first,1}]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec association(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
association(Key, Options) ->
    Fold =
    fun (Fun, Acc, #{Key:=Value}) ->
            {ok, Fun({Key, Value}, Acc)};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, #{Key:=Value} = Map) when is_map(Map) ->
            {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, Acc),
            {ok, {(maps:remove(Key, Map))#{NewKey=>NewValue}, NewAcc}};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (Map, Template) when is_map(Map) ->
            Map#{Key=>Template};
        (_Data, Template) ->
            #{Key=>Template}
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).


<|start_filename|>test/test_optic_orddict.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_orddict.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_orddict).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(optic:get([optic_orddict:all([strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{one, 4}, {two, 4}, {three, 4}])},
                   optic:put([optic_orddict:all([strict])],
                             4,
                             orddict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, orddict:new()},
                 optic:put([optic_orddict:all([strict, create])], 4, atom)).

keys_get_test_() ->
    [?_assertEqual({ok, [one, three, two]},
                   sort_get(optic:get([optic_orddict:keys([strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:keys([strict])], atom))].

keys_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{four, 1}])},
                   optic:put([optic_orddict:keys([strict])],
                             four,
                             orddict:from_list([{one, 1}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:keys([strict])], four, atom))].

keys_create_test() ->
    ?assertEqual({ok, orddict:new()},
                 optic:put([optic_orddict:keys([strict, create])], four, atom)).

values_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(optic:get([optic_orddict:values([strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:values([strict])], atom))].

values_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{one, 4}, {two, 4}, {three, 4}])},
                   optic:put([optic_orddict:values([strict])],
                             4,
                             orddict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:values([strict])], 4, atom))].

values_create_test() ->
    ?assertEqual({ok, orddict:new()},
                 optic:put([optic_orddict:values([strict, create])], 4, atom)).

associations_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {three, 3}, {two, 2}]},
                   sort_get(optic:get([optic_orddict:associations([strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:associations([strict])], atom))].

associations_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{four, 4}])},
                   optic:put([optic_orddict:associations([strict])],
                             {four, 4},
                             orddict:from_list([{one, 1}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:associations([strict])],
                             {four, 4},
                             atom))].

associations_create_test() ->
    ?assertEqual({ok, orddict:new()},
                 optic:put([optic_orddict:associations([strict, create])],
                           {four, 4},
                           atom)).

key_get_test_() ->
    [?_assertEqual({ok, [1]},
                   sort_get(optic:get([optic_orddict:key(one, [strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:key(one, [strict])], atom))].

key_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{one, 4}, {two, 2}, {three, 3}])},
                   optic:put([optic_orddict:key(one, [strict])],
                             4,
                             orddict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:key(one, [strict])], 4, atom))].

key_create_test_() ->
    [?_assertEqual({ok, orddict:from_list([{one, 1}, {four, 4}])},
                   optic:put([optic_orddict:key(four, [strict, create])],
                             4,
                             orddict:from_list([{one, 1}]))),
     ?_assertEqual({ok, orddict:from_list([{four, 4}])},
                   optic:put([optic_orddict:key(four, [strict, create])],
                             4,
                             atom))].

association_get_test_() ->
    [?_assertEqual({ok, [{one, 1}]},
                   sort_get(optic:get([optic_orddict:association(one, [strict])],
                                      orddict:from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_orddict:association(one, [strict])], atom))].

association_put_test_() ->
    [?_assertEqual({ok, orddict:from_list([{two, 2}, {three, 3}, {four, 4}])},
                   optic:put([optic_orddict:association(one, [strict])],
                             {four, 4},
                             orddict:from_list([{one, 1}, {two, 2}, {three, 3}]))),
     ?_assertEqual({ok, orddict:from_list([{two, 2}, {three, 3}, {four, 4}])},
                   optic:put([optic_orddict:association(one, [strict])],
                             {four, 4},
                             orddict:from_list([{one, 1}, {two, 2}, {three, 3}, {four, clobbered}]))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_orddict:association(one, [strict])],
                             4,
                             atom))].

association_create_test_() ->
    [?_assertEqual({ok, orddict:from_list([{one, 1}, {four, 4}])},
                   optic:put([optic_orddict:association(four, [strict, create])],
                             {four, 4},
                             orddict:from_list([{one, 1}]))),
     ?_assertEqual({ok, orddict:from_list([{four, 4}])},
                   optic:put([optic_orddict:association(four, [strict, create])],
                             {four, 4},
                             atom))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

sort_get({ok, Result}) ->
    {ok, lists:sort(Result)}.


<|start_filename|>src/optic_proplists.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to proplists.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_proplists).

%% API
-export([all/0,
         all/1,
         keys/0,
         keys/1,
         values/0,
         values/1,
         properties/0,
         properties/1,
         key/1,
         key/2,
         property/1,
         property/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see values/1
-spec all() -> optic:optic().
all() ->
    values(#{}).

%% @see values/1
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    values(Options).

%% @see keys/1
-spec keys() -> optic:optic().
keys() ->
    keys(#{}).

%% @doc
%% Focus on all keys of a list of properties. Duplicate keys are
%% preserved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_proplists:keys()], [{first, 1}, {second, 2}]).
%% {ok,[first,second]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec keys(Options) -> optic:optic() when
      Options :: optic:variations().
keys(Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            {ok, lists:foldl(
                   fun ({Key, _Value}, InnerAcc) ->
                           Fun(Key, InnerAcc)
                   end,
                   Acc,
                   proplists:unfold(List))};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            {NewList, NewAcc} = lists:foldl(
                                  fun ({Key, Value}, {InnerList, InnerAcc}) ->
                                          {NewKey, NewAcc} = Fun(Key, InnerAcc),
                                          {[{NewKey, Value} | InnerList], NewAcc}
                                  end,
                                  {[], Acc},
                                  proplists:unfold(List)),
            {ok, {lists:reverse(NewList), NewAcc}};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            []
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see values/1
-spec values() -> optic:optic().
values() ->
    values(#{}).

%% @doc
%% Focus on all values of a list of properties.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_proplists:values()], [{first, 1}, {second, 2}]).
%% {ok,[1,2]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec values(Options) -> optic:optic() when
    Options :: optic:variations().
values(Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            {ok, lists:foldl(
                   fun ({_Key, Value}, InnerAcc) ->
                           Fun(Value, InnerAcc)
                   end,
                   Acc,
                   proplists:unfold(List))};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            {NewList, NewAcc} = lists:foldl(
                                  fun ({Key, Value}, {InnerList, InnerAcc}) ->
                                          {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                          {[{Key, NewValue} | InnerList], NewAcc}
                                  end,
                                  {[], Acc},
                                  proplists:unfold(List)),
            {ok, {lists:reverse(NewList), NewAcc}};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            []
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see properties/1
-spec properties() -> optic:optic().
properties() ->
    properties(#{}).

%% @doc
%% Focus on all properties of a list of properties. A propety is a
%% tuple of a key and value. If a value was not given, it defaults to
%% the atom `true'.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_proplists:properties()], [{first, 1}, {second, 2}]).
%% {ok,[{first,1},{second,2}]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec properties(Options) -> optic:optic() when
      Options :: optic:variations().
properties(Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            {ok, lists:foldl(Fun, Acc, proplists:unfold(List))};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            {NewList, NewAcc} = lists:foldl(
                                  fun (Tuple, {InnerList, InnerAcc}) ->
                                          {NewTuple, NewAcc} = Fun(Tuple, InnerAcc),
                                          {[NewTuple | InnerList], NewAcc}
                                  end,
                                  {[], Acc},
                                  proplists:unfold(List)),
            {ok, {lists:reverse(NewList), NewAcc}};
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (_Data, _Template) ->
            []
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see key/2
-spec key(Key) -> optic:optic() when
      Key :: term().
key(Key) ->
    key(Key, #{}).

%% @doc
%% Focus on the value of a property list key. As keys may be
%% duplicated, this may be multiple values. If the value is not given,
%% it defaults to the atom `true'.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_proplists:key(first)], [{first, 1}, {second, 2}]).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec key(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
key(Key, Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            case proplists:get_all_values(Key, List) of
                [] ->
                    {error, undefined};
                Values ->
                    {ok, lists:foldl(
                           fun (Value, InnerAcc) ->
                                   Fun(Value, InnerAcc)
                           end,
                           Acc,
                           Values)}
            end;
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            case proplists:get_all_values(Key, List) of
                [] ->
                    {error, undefined};
                _ ->
                    {ok, lists:mapfoldl(
                           fun (Elem, InnerAcc) ->
                                   case proplists:is_defined(Key, [Elem]) of
                                       true ->
                                           Value = proplists:get_value(Key, [Elem]),
                                           {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                           {{Key, NewValue}, NewAcc};
                                       false ->
                                           {Elem, InnerAcc}
                                   end
                           end,
                           Acc,
                           List)}
            end;
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (List, Template) when is_list(List) ->
            [{Key, Template} | List];
        (_Data, Template) ->
            [{Key, Template}]
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see property/2
-spec property(Key) -> optic:optic() when
      Key :: term().
property(Key) ->
    property(Key, #{}).

%% @doc
%% Focus on a property in a property list by key. As keys may be
%% duplicated, this may be multiple properties. If the value is not
%% given, it defaults to the atom `true'. If the key is modified, the
%% optic is no longer well behaved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_proplists:property(first)], [{first, 1}, {second, 2}]).
%% {ok,[{first,1}]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec property(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
property(Key, Options) ->
    Fold =
    fun (Fun, Acc, List) when is_list(List) ->
            case proplists:get_all_values(Key, List) of
                [] ->
                    {error, undefined};
                Values ->
                    {ok, lists:foldl(
                           fun (Value, InnerAcc) ->
                                   Fun({Key, Value}, InnerAcc)
                           end,
                           Acc,
                           Values)}
            end;
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    MapFold =
    fun (Fun, Acc, List) when is_list(List) ->
            case proplists:get_all_values(Key, List) of
                [] ->
                    {error, undefined};
                _ ->
                    {ok, lists:mapfoldl(
                           fun (Elem, InnerAcc) ->
                                   case proplists:is_defined(Key, [Elem]) of
                                       true ->
                                           Value = proplists:get_value(Key, [Elem]),
                                           {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, InnerAcc),
                                           {{NewKey, NewValue}, NewAcc};
                                       false ->
                                           {Elem, InnerAcc}
                                   end
                           end,
                           Acc,
                           List)}
            end;
        (_Fun, _Acc, _Data) ->
            {error, undefined}
    end,
    New =
    fun (List, Template) when is_list(List) ->
            [{Key, Template} | List];
        (_Data, Template) ->
            [{Key, Template}]
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).


<|start_filename|>doc/optic_array.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_array</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_array</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
   A set of optics specific to arrays.


<h2><a name="description">Description</a></h2>
   A set of optics specific to arrays.
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#all-0">all/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#all-1">all/1</a></td><td>  
Focus on all values of an array.</td></tr>
<tr><td valign="top"><a href="#nth-1">nth/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#nth-2">nth/2</a></td><td>  
Focus on the nth value of an array.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="all-0">all/0</a></h3>
<div class="spec">
<p><tt>all() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#all-1">all/1</a>.</p>

<h3 class="function"><a name="all-1">all/1</a></h3>
<div class="spec">
<p><tt>all(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all values of an array.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_array:all()], array:from_list([1,2,3])).
  {ok,[1,2,3]}</pre></p>

<h3 class="function"><a name="nth-1">nth/1</a></h3>
<div class="spec">
<p><tt>nth(N) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#nth-2">nth/2</a>.</p>

<h3 class="function"><a name="nth-2">nth/2</a></h3>
<div class="spec">
<p><tt>nth(N, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>N</tt>: The index of the array value to focus on.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the nth value of an array. Like lists, but unlike the  
standard array operations, indexing begins at 1.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_array:nth(1)], array:from_list([1,2,3])).
  {ok,[1]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>src/optic_dict.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to dicts.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_dict).

%% API
-export([all/0,
         all/1,
         keys/0,
         keys/1,
         values/0,
         values/1,
         associations/0,
         associations/1,
         key/1,
         key/2,
         association/1,
         association/2]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see values/1
-spec all() -> optic:optic().
all() ->
    values().

%% @see values/1
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    values(Options).

%% @see keys/1
-spec keys() -> optic:optic().
keys() ->
    keys(#{}).

%% @doc
%% Focus on all keys of a dict.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_dict:keys()],
%%             dict:from_list([{first, 1}, {second, 2}])).
%% {ok,[first,second]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec keys(Options) -> optic:optic() when
      Options :: optic:variations().
keys(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (Key, _Value, InnerAcc) ->
                                           Fun(Key, InnerAcc)
                                   end,
                                   Acc,
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                           {NewKey, NewAcc} = Fun(Key, InnerAcc),
                                           {dict:store(NewKey, Value, InnerDict), NewAcc}
                                   end,
                                   {dict:new(), Acc},
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            dict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see values/1
-spec values() -> optic:optic().
values() ->
    values(#{}).

%% @doc
%% Focus on all values of a dict.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_dict:values()],
%%             dict:from_list([{first, 1}, {second, 2}])).
%% {ok,[1,2]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec values(Options) -> optic:optic() when
      Options :: optic:variations().
values(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (_Key, Value, InnerAcc) ->
                                           Fun(Value, InnerAcc)
                                   end,
                                   Acc,
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                           {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                           {dict:store(Key, NewValue, InnerDict), NewAcc}
                                   end,
                                   {dict:new(), Acc},
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            dict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see associations/1
-spec associations() -> optic:optic().
associations() ->
    associations(#{}).

%% @doc
%% Focus on all associations of a dict. An association is a tuple of
%% the key and value for each entry.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_dict:associations()],
%%             dict:from_list([{first, 1}, {second, 2}])).
%% {ok,[{first,1},{second,2}]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec associations(Options) -> optic:optic() when
      Options :: optic:variations().
associations(Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (Key, Value, InnerAcc) ->
                                           Fun({Key, Value}, InnerAcc)
                                   end,
                                   Acc,
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    {ok, dict:fold(fun (Key, Value, {InnerDict, InnerAcc}) ->
                                           {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, InnerAcc),
                                           {dict:store(NewKey, NewValue, InnerDict), NewAcc}
                                   end,
                                   {dict:new(), Acc},
                                   Dict)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            dict:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see key/2
-spec key(Key) -> optic:optic() when
    Key :: term().
key(Key) ->
    key(Key, #{}).

%% @doc
%% Focus on the value of a dict key.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_dict:key(first)],
%%             dict:from_list([{first, 1}, {second, 2}])).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec key(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
key(Key, Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {ok, Fun(Value, Acc)};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {NewValue, NewAcc} = Fun(Value, Acc),
                            {ok, {dict:store(Key, NewValue, Dict), NewAcc}};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Dict, Template) ->
            case is_dict(Dict) of
                true ->
                    dict:store(Key, Template, Dict);
                false ->
                    dict:from_list([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%% @see association/2
-spec association(Key) -> optic:optic() when
      Key :: term().
association(Key) ->
    association(Key, #{}).

%% @doc
%% Focus on the association for a dict key. An association is the
%% tuple of a dict key and value. If the key is modified, the optic is
%% no longer well behaved.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_dict:association(first)],
%%             dict:from_list([{first, 1}, {second, 2}])).
%% {ok,[{first,1}]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec association(Key, Options) -> optic:optic() when
      Key :: term(),
      Options :: optic:variations().
association(Key, Options) ->
    Fold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {ok, Fun({Key, Value}, Acc)};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Dict) ->
            case is_dict(Dict) of
                true ->
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {{NewKey, NewValue}, NewAcc} = Fun({Key, Value}, Acc),
                            {ok, {dict:store(NewKey, NewValue, dict:erase(Key, Dict)), NewAcc}};
                        error ->
                            {error, undefined}
                    end;
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (Dict, Template) ->
            case is_dict(Dict) of
                true ->
                    dict:store(Key, Template, Dict);
                false ->
                    dict:from_list([{Key, Template}])
            end
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

is_dict(Unknown) ->
    try dict:size(Unknown) of
        _ ->
            true
    catch
        error:function_clause ->
            false
    end.


<|start_filename|>src/optic_sets.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to sets.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_sets).

%% API
-export([all/0,
         all/1]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see all/1
-spec all() -> optic:optic().
all() ->
    all(#{}).

%% @doc
%% Focus on all elements of a set.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_sets:all()], sets:from_list([1,2,3])).
%% {ok,[1,2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    Fold =
    fun (Fun, Acc, Set) ->
            case is_set(Set) of
                true ->
                    {ok, sets:fold(Fun, Acc, Set)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Set) ->
            case is_set(Set) of
                true ->
                    {ok, sets:fold(fun (Elem, {InnerSet, InnerAcc}) ->
                                           {NewElem, NewAcc} = Fun(Elem, InnerAcc),
                                           {sets:add_element(NewElem, InnerSet), NewAcc}
                                   end,
                                   {sets:new(), Acc},
                                   Set)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            sets:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

is_set(Unknown) ->
    try sets:size(Unknown) of
        _ ->
            true
    catch
        error:{badrecord, set} ->
            false
    end.


<|start_filename|>src/optic_path.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Utility functions for constructing optics from lists of selectors.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_path).

-type path() :: string() | binary() | non_neg_integer() | '*'.
%% A single path component.
-type paths() :: [path()].
%% A list of path components.

%% API
-export([new/1]).

-export_type([path/0,
              paths/0]).

%%%===================================================================
%%% API
%%%===================================================================

%% @doc
%% Construct a list of optics from a path. The type of the path
%% component determines the optic used:
%%
%% <ul>
%% <li>string: A key for a map-like structure.</li>
%% <li>binary: A key for a map-like structure.</li>
%% <li>integer: An index into a list-like structure.</li>
%% <li>'*': All elements of a list.</li>
%% </ul>
%%
%% This heavily depends on the `optic_generic' module, see the optics
%% there for the full list of containers supported.
%%
%% Example:
%%
%% ```
%% > optic:get(optic_path(["first"]), ${"first" => 1, "second" => 2}).
%% {ok,[1]}
%% '''
%% @end
%% @param Paths A list of path components to convert.
%% @returns A list of opaque optic records.
-spec new(Paths) -> optic:optics() when
      Paths :: paths().
new([]) ->
    [];
new([Path | Paths]) when is_list(Path); is_binary(Path) ->
    [optic_generic:key(Path) | new(Paths)];
new([Path | Paths]) when is_integer(Path), Path > 0 ->
    [optic_generic:index(Path) | new(Paths)];
new(['*' | Paths]) ->
    [optic_lists:all() | new(Paths)].


<|start_filename|>doc/optic_tuples.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_tuples</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_tuples</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
   A set of optics specific to tuples.


<h2><a name="description">Description</a></h2>
   A set of optics specific to tuples.
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#all-0">all/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#all-1">all/1</a></td><td>  
Focus on all elements of a tuple.</td></tr>
<tr><td valign="top"><a href="#element-1">element/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#element-2">element/2</a></td><td>
  Focus on the nth element of a tuple.</td></tr>
<tr><td valign="top"><a href="#field-3">field/3</a></td><td></td></tr>
<tr><td valign="top"><a href="#field-4">field/4</a></td><td>
  Focus on a record field.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="all-0">all/0</a></h3>
<div class="spec">
<p><tt>all() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#all-1">all/1</a>.</p>

<h3 class="function"><a name="all-1">all/1</a></h3>
<div class="spec">
<p><tt>all(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all elements of a tuple.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_tuples:all()], {1,2,3}).
  {ok,[1,2,3]}</pre></p>

<h3 class="function"><a name="element-1">element/1</a></h3>
<div class="spec">
<p><tt>element(N) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#element-2">element/2</a>.</p>

<h3 class="function"><a name="element-2">element/2</a></h3>
<div class="spec">
<p><tt>element(N, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>N</tt>: The index of the tuple element to focus on.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>
  Focus on the nth element of a tuple. As with <code>erlang:element/2</code>,  
indexing begins at 1.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_tuples:element(1)], {1,2,3}).
  {ok,[1]}</pre></p>

<h3 class="function"><a name="field-3">field/3</a></h3>
<div class="spec">
<p><tt>field(Tag, Size, N) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Tag = atom()</tt></li><li><tt>Size = pos_integer()</tt></li><li><tt>N = pos_integer()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#field-4">field/4</a>.</p>

<h3 class="function"><a name="field-4">field/4</a></h3>
<div class="spec">
<p><tt>field(Tag, Size, N, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Tag = atom()</tt></li><li><tt>Size = pos_integer()</tt></li><li><tt>N = pos_integer()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Tag</tt>: The expected record tag.<br>
<tt>Size</tt>: The expected record size.<br>
<tt>N</tt>: The index of the field in the record tuple.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>
  Focus on a record field. As records are a compiler construct, this
  depends on the <code>?OPTIC_FIELD</code> macro in <code>include/optic_tuples.hrl</code>  
to construct the required arguments from the record definition.</p>
 
  <p>Given the record definition:</p>
 
  <pre>  -include_lib("optic/include/optic_tuples.hrl").
  -record(example, {first}).</pre>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_tuples:field(?OPTIC_FIELD(example, first))],
              #example{first=1}).
  {ok,[1]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>test/test_optic_maps.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_maps.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_maps).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(optic:get([optic_maps:all([strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, #{one=>4, two=>4, three=>4}},
                   optic:put([optic_maps:all([strict])],
                             4,
                             #{one=>1, two=>2, three=>3})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, #{}},
                 optic:put([optic_maps:all([strict, create])], 4, atom)).

keys_get_test_() ->
    [?_assertEqual({ok, [one, three, two]},
                   sort_get(optic:get([optic_maps:keys([strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:keys([strict])], atom))].

keys_put_test_() ->
    [?_assertEqual({ok, #{four=>1}},
                   optic:put([optic_maps:keys([strict])],
                             four,
                             #{one=>1})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:keys([strict])], four, atom))].

keys_create_test() ->
    ?assertEqual({ok, #{}},
                 optic:put([optic_maps:keys([strict, create])], four, atom)).

values_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(optic:get([optic_maps:values([strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:values([strict])], atom))].

values_put_test_() ->
    [?_assertEqual({ok, #{one=>4, two=>4, three=>4}},
                   optic:put([optic_maps:values([strict])],
                             4,
                             #{one=>1, two=>2, three=>3})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:values([strict])], 4, atom))].

values_create_test() ->
    ?assertEqual({ok, #{}},
                 optic:put([optic_maps:values([strict, create])], 4, atom)).

associations_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {three, 3}, {two, 2}]},
                   sort_get(optic:get([optic_maps:associations([strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:associations([strict])], atom))].

associations_put_test_() ->
    [?_assertEqual({ok, #{four=>4}},
                   optic:put([optic_maps:associations([strict])],
                             {four, 4},
                             #{one=>1})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:associations([strict])],
                             {four, 4},
                             atom))].

associations_create_test() ->
    ?assertEqual({ok, #{}},
                 optic:put([optic_maps:associations([strict, create])],
                           {four, 4},
                           atom)).

key_get_test_() ->
    [?_assertEqual({ok, [1]},
                   sort_get(optic:get([optic_maps:key(one, [strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:key(one, [strict])], atom))].

key_put_test_() ->
    [?_assertEqual({ok, #{one=>4, two=>2, three=>3}},
                   optic:put([optic_maps:key(one, [strict])],
                             4,
                             #{one=>1, two=>2, three=>3})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:key(one, [strict])], 4, atom))].

key_create_test_() ->
    [?_assertEqual({ok, #{one=>1, four=>4}},
                   optic:put([optic_maps:key(four, [strict, create])],
                             4,
                             #{one=>1})),
     ?_assertEqual({ok, #{four=>4}},
                   optic:put([optic_maps:key(four, [strict, create])],
                             4,
                             atom))].

association_get_test_() ->
    [?_assertEqual({ok, [{one, 1}]},
                   sort_get(optic:get([optic_maps:association(one, [strict])],
                                      #{one=>1, two=>2, three=>3}))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_maps:association(one, [strict])], atom))].

association_put_test_() ->
    [?_assertEqual({ok, #{two=>2, three=>3, four=>4}},
                   optic:put([optic_maps:association(one, [strict])],
                             {four, 4},
                             #{one=>1, two=>2, three=>3})),
     ?_assertEqual({ok, #{two=>2, three=>3, four=>4}},
                   optic:put([optic_maps:association(one, [strict])],
                             {four, 4},
                             #{one=>1, two=>2, three=>3, four=>clobbered})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_maps:association(one, [strict])],
                             4,
                             atom))].

association_create_test_() ->
    [?_assertEqual({ok, #{one=>1, four=>4}},
                   optic:put([optic_maps:association(four, [strict, create])],
                             {four, 4},
                             #{one=>1})),
     ?_assertEqual({ok, #{four=>4}},
                   optic:put([optic_maps:association(four, [strict, create])],
                             {four, 4},
                             atom))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

sort_get({ok, Result}) ->
    {ok, lists:sort(Result)}.


<|start_filename|>test/test_optic.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

new_test_() ->
    Fold = fun (Fun, Acc, Data) ->
                   {ok, Fun(Data, Acc)}
           end,
    [?_assert(optic:is_optic(optic:new(Fold))),
     ?_assert(optic:is_optic(optic:new(Fold, Fold)))].

wrap_test_() ->
    Wrapped2 = optic:wrap(
                 id(),
                 fun (MapFold) ->
                         MapFold
                 end),
    Wrapped3 = optic:wrap(
                 id(),
                 fun (MapFold) ->
                         MapFold
                 end,
                 fun (Fold) ->
                         Fold
                 end),
    [?_assert(optic:is_optic(Wrapped2)),
     ?_assert(optic:is_optic(Wrapped3)),
     ?_assertEqual({ok, [atom]},
                   optic:get(Wrapped2, atom)),
     ?_assertEqual({ok, modified},
                   optic:put(Wrapped2, modified, atom)),
     ?_assertEqual({ok, [atom]},
                   optic:get(Wrapped3, atom)),
     ?_assertEqual({ok, modified},
                   optic:put(Wrapped3, modified, atom))].

chain_test_() ->
    [?_assert(optic:is_optic(optic:chain([id(), id(), id()]))),
     ?_assertEqual({ok, [atom]},
                   optic:get(optic:chain([id()]),
                             atom)),
     ?_assertEqual({ok, [atom]},
                   optic:get(optic:chain([id(), id()]),
                             atom)),
     ?_assertEqual({ok, [atom]},
                   optic:get(optic:chain([id(), id(), id()]),
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put(optic:chain([id()]),
                             modified,
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put(optic:chain([id(), id()]),
                             modified,
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put(optic:chain([id(), id(), id()]),
                             modified,
                             atom))].

merge_test_() ->
    [?_assert(optic:is_optic(optic:merge([id(), id(), id()]))),
     ?_assertEqual({ok, [atom]},
                   optic:get([optic:merge([id()])],
                             atom)),
     ?_assertEqual({ok, [atom, atom]},
                   optic:get([optic:merge([id(), id()])],
                             atom)),
     ?_assertEqual({ok, [atom, atom, atom]},
                   optic:get([optic:merge([id(), id(), id()])],
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put([optic:merge([id()])],
                             modified,
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put([optic:merge([id(), id()])],
                             modified,
                             atom)),
     ?_assertEqual({ok, modified},
                   optic:put([optic:merge([id(), id(), id()])],
                             modified,
                             atom))].

is_optic_test_() ->
    [?_assert(optic:is_optic(id())),
     ?_assert(not optic:is_optic({}))].

variations_test_() ->
    New = fun (_Data, _Template) -> undefined end,
    True = fun true/1,
    [?_assert(optic:is_optic(
                optic:variations(id(), #{}, New))),
     ?_assert(optic:is_optic(
                optic:variations(id(), #{strict=>false}, New))),
     ?_assert(optic:is_optic(
                optic:variations(id(), #{strict=>true}, New))),
     ?_assert(optic:is_optic(
                optic:variations(id(), #{create=>undefined}, New))),
     ?_assert(optic:is_optic(
                optic:variations(id(), #{filter=>fun true/1}, New))),
     ?_assert(optic:is_optic(
                optic:variations(id(), #{require=>fun true/1}, New))),
     ?_assertEqual(optic:variations(id(), #{}, New),
                   optic:variations(id(), #{strict=>false}, New)),
     ?_assertEqual(optic:variations(id(), [], New),
                   optic:variations(id(), #{strict=>false}, New)),
     ?_assertEqual(optic:variations(id(), [strict], New),
                   optic:variations(id(), #{strict=>true}, New)),
     ?_assertEqual(optic:variations(id(), [create], New),
                   optic:variations(id(), #{create=>true}, New)),
     ?_assertEqual(optic:variations(id(), [{filter, True}], New),
                   optic:variations(id(), #{filter=>True}, New)),
     ?_assertEqual(optic:variations(id(), [{require, True}], New),
                   optic:variations(id(), #{require=>True}, New))].

create_test_() ->
    Create = optic:create(
               optic:require(fun erlang:is_list/1),
               fun (_Data, _Template) -> [] end,
               template),
    [?_assertEqual({ok, [[]]},
                   optic:get(Create, [])),
     ?_assertEqual({error, required},
                   optic:get(Create, atom)),
     ?_assertEqual({ok, modified},
                   optic:put(Create, modified, [])),
     ?_assertEqual({ok, modified},
                   optic:put(Create, modified, atom))].

lax_test_() ->
    Lax = optic:lax(optic:require(fun erlang:is_list/1)),
    [?_assertEqual({ok, [[]]},
                   optic:get(Lax, [])),
     ?_assertEqual({ok, []},
                   optic:get(Lax, atom)),
     ?_assertEqual({ok, modified},
                   optic:put(Lax, modified, [])),
     ?_assertEqual({ok, atom},
                   optic:put(Lax, modified, atom))].

fold_test() ->
    ?assertEqual({ok, 2},
                 optic:fold([id()],
                            fun (atom, Acc) -> Acc + 1 end,
                            1,
                            atom)).

get_test() ->
    ?assertEqual({ok, [atom]},
                 optic:get([id()], atom)).

mapfold_test() ->
    ?assertEqual({ok, {modified, 2}},
                 optic:mapfold([id()],
                               fun (atom, Acc) ->
                                       {modified, Acc + 1}
                               end,
                               1,
                               atom)).

map_test() ->
    ?assertEqual({ok, modified},
                 optic:map([id()],
                           fun (atom) -> modified end,
                           atom)).

put_test() ->
    ?assertEqual({ok, modified},
                 optic:put([id()], modified, atom)).

id_get_test() ->
    ?assertEqual({ok, [atom]},
                 optic:get(optic:id(), atom)).

id_put_test() ->
    ?assertEqual({ok, new},
                 optic:put(optic:id(), new, atom)).

error_get_test() ->
    ?assertEqual({error, reason},
                 optic:get(optic:error(reason), atom)).

error_put_test() ->
    ?assertEqual({error, reason},
                 optic:put(optic:error(reason), new, atom)).

filter_get_test_() ->
    [?_assertEqual({ok, [atom]},
                   optic:get(optic:filter(fun true/1), atom)),
     ?_assertEqual({ok, []},
                   optic:get(optic:filter(fun false/1), atom))].

filter_put_test_() ->
    [?_assertEqual({ok, modified},
                   optic:put(optic:filter(fun true/1), modified, atom)),
     ?_assertEqual({ok, atom},
                   optic:put(optic:filter(fun false/1), modified, atom))].

require_get_test_() ->
    [?_assertEqual({ok, [atom]},
                   optic:get(optic:require(fun true/1), atom)),
     ?_assertEqual({error, required},
                   optic:get(optic:require(fun false/1), atom))].

require_put_test_() ->
    [?_assertEqual({ok, modified},
                   optic:put(optic:require(fun true/1), modified, atom)),
     ?_assertEqual({error, required},
                   optic:put(optic:require(fun false/1), modified, atom))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

id() ->
    MapFold = fun (Fun, Acc, Data) ->
                      {ok, Fun(Data, Acc)}
              end,
    optic:new(MapFold).

true(_) ->
    true.

false(_) ->
    false.


<|start_filename|>test/test_optic_generic.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_generic.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_generic).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

key_get_test_() ->
    [?_assertEqual({ok, [value]},
                   optic:get([optic_generic:key(key)], #{key=>value})),
     ?_assertEqual({ok, [value]},
                   optic:get([optic_generic:key(key)], [{key, value}])),
     ?_assertEqual({ok, [value]},
                   optic:get([optic_generic:key(key)], orddict:from_list([{key, value}]))),
     ?_assertEqual({ok, [value]},
                   optic:get([optic_generic:key(key)], dict:from_list([{key, value}]))),
     ?_assertEqual({ok, [value]},
                   optic:get([optic_generic:key(key)], gb_trees:from_orddict([{key, value}]))),
     ?_assertEqual({ok, []},
                   optic:get([optic_generic:key(key)], atom))].

key_put_test_() ->
    [?_assertEqual({ok, #{key=>new}},
                   optic:put([optic_generic:key(key)], new, #{key=>value})),
     ?_assertEqual({ok, [{key, new}]},
                   optic:put([optic_generic:key(key)], new, [{key, value}])),
     ?_assertEqual({ok, [{key, new}]},
                   optic:put([optic_generic:key(key)], new, orddict:from_list([{key, value}]))),
     ?_assertEqual({ok, dict:from_list([{key, new}])},
                   optic:put([optic_generic:key(key)], new, dict:from_list([{key, value}]))),
     ?_assertEqual({ok, gb_trees:from_orddict([{key, new}])},
                   optic:put([optic_generic:key(key)], new, gb_trees:from_orddict([{key, value}]))),
     ?_assertEqual({ok, atom},
                   optic:put([optic_generic:key(key)], new, atom))].

index_get_test_() ->
    [?_assertEqual({ok, [3]},
                   optic:get([optic_generic:index(3)], [1, 2, 3])),
     ?_assertEqual({ok, [3]},
                   optic:get([optic_generic:index(3)], {1, 2, 3})),
     ?_assertEqual({ok, []},
                   optic:get([optic_generic:index(3)], #{}))].

index_put_test_() ->
    [?_assertEqual({ok, [1, 2, 4]},
                   optic:put([optic_generic:index(3)], 4, [1, 2, 3])),
     ?_assertEqual({ok, {1, 2, 4}},
                   optic:put([optic_generic:index(3)], 4, {1, 2, 3})),
     ?_assertEqual({ok, #{}},
                   optic:put([optic_generic:index(3)], 4, #{}))].


<|start_filename|>src/optic_generic.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of generic optics that can be applied to multiple container
%%% types.
%%%
%%% Intended both as a convenience and to support optic creation from
%%% parsed paths in optic_path.
%%%
%%% Because of the ambiguous types they support, these optics do not
%%% support the standard optic options. Instead, they always skip
%%% unexpected types and never create missing values.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_generic).

%% API
-export([key/1,
         index/1]).

%%%===================================================================
%%% API
%%%===================================================================

%% @doc
%% Focus on the value of many different key/value like mappings.
%% Understands how to focus on maps, property lists, dicts, orddicts
%% and gb_trees. Does not support the usual optic options.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_generic:key(first)], #{first => 1}).
%% {ok,[1]}
%% '''
%% @end
%% @param Key The key to focus on.
%% @returns An opaque optic record.
-spec key(Key) -> optic:optic() when
      Key :: term().
key(Key) ->
    Fold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            case Map of
                #{Key:=Value} ->
                    {ok, Fun(Value, Acc)};
                _ ->
                    {ok, Acc}
            end;
        (Fun, Acc, List) when is_list(List) ->
            % This might be a proplist or an orddict.
            Values = proplists:get_all_values(Key, List),
            {ok, lists:foldl(Fun, Acc, Values)};
        (Fun, Acc, Unknown) ->
            case {is_dict(Unknown), is_gb_tree(Unknown)} of
                {true, _} ->
                    % It's bad if is_dict & is_gb_tree ever both return
                    % true, but I can't justify throwing an error when
                    % this is already doing such fuzzy guessing of types.
                    Dict = Unknown,
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {ok, Fun(Value, Acc)};
                        error ->
                            {ok, Acc}
                    end;
                {false, true} ->
                    Tree = Unknown,
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {ok, Fun(Value, Acc)};
                        none ->
                            {ok, Acc}
                    end;
                {false, false} ->
                    {ok, Acc}
            end
    end,
    MapFold =
    fun (Fun, Acc, Map) when is_map(Map) ->
            case Map of
                #{Key:=Value} ->
                    {NewValue, NewAcc} = Fun(Value, Acc),
                    {ok, {Map#{Key:=NewValue}, NewAcc}};
                _ ->
                    {ok, {Map, Acc}}
            end;
        (Fun, Acc, List) when is_list(List) ->
            % This might be a proplist or an orddict.
            {ok, lists:mapfoldl(fun (Elem, InnerAcc) ->
                                        case proplists:is_defined(Key, [Elem]) of
                                            true ->
                                                Value = proplists:get_value(Key, [Elem]),
                                                {NewValue, NewAcc} = Fun(Value, InnerAcc),
                                                {{Key, NewValue}, NewAcc};
                                            false ->
                                                {Elem, InnerAcc}
                                        end
                                end,
                                Acc,
                                List)};
        (Fun, Acc, Unknown) ->
            case {is_dict(Unknown), is_gb_tree(Unknown)} of
                {true, _} ->
                    % It's bad if is_dict & is_gb_tree ever both return
                    % true, but I can't justify throwing an error when
                    % this is already doing such fuzzy guessing of types.
                    Dict = Unknown,
                    case dict:find(Key, Dict) of
                        {ok, Value} ->
                            {NewValue, NewAcc} = Fun(Value, Acc),
                            {ok, {dict:store(Key, NewValue, Dict), NewAcc}};
                        error ->
                            {ok, {Dict, Acc}}
                    end;
                {false, true} ->
                    Tree = Unknown,
                    case gb_trees:lookup(Key, Tree) of
                        {value, Value} ->
                            {NewValue, NewAcc} = Fun(Value, Acc),
                            {ok, {gb_trees:update(Key, NewValue, Tree), NewAcc}};
                        none ->
                            {ok, {Tree, Acc}}
                    end;
                {false, false} ->
                    {ok, {Unknown, Acc}}
            end
    end,
    optic:new(MapFold, Fold).

%% @doc
%% Focus on an element of a list like container. Indexing begins at 1.
%% Understands how to focus on lists and tuples. Does not support the
%% usual optic options.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_generic:index(3)], [1, 2, 3]).
%% {ok,[3]}
%% '''
%% @end
%% @param Index The one based index of the element to focus on.
%% @returns An opaque optic record.
-spec index(Index) -> optic:optic() when
      Index :: non_neg_integer().
index(Index) ->
    Fold =
    fun (Fun, Acc, List) when Index =< length(List) ->
            Elem = lists:nth(Index, List),
            {ok, Fun(Elem, Acc)};
        (Fun, Acc, Tuple) when Index =< tuple_size(Tuple) ->
            Elem = erlang:element(Index, Tuple),
            {ok, Fun(Elem, Acc)};
        (_Fun, Acc, _Data) ->
            {ok, Acc}
    end,
    MapFold =
    fun (Fun, Acc, List) when Index =< length(List) ->
            {Before, [Head | Tail]} = lists:split(Index - 1, List),
            {NewHead, NewAcc} = Fun(Head, Acc),
            {ok, {Before ++ [NewHead] ++ Tail, NewAcc}};
        (Fun, Acc, Tuple) when Index =< tuple_size(Tuple) ->
            Elem = erlang:element(Index, Tuple),
            {NewElem, NewAcc} = Fun(Elem, Acc),
            {ok, {erlang:setelement(Index, Tuple, NewElem), NewAcc}};
        (_Fun, Acc, Unknown) ->
            {ok, {Unknown, Acc}}
    end,
    optic:new(MapFold, Fold).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

is_dict(Unknown) ->
    try dict:size(Unknown) of
        _ ->
            true
    catch
        error:function_clause ->
            false
    end.

is_gb_tree(Unknown) ->
    try gb_trees:size(Unknown) of
        _ ->
            true
    catch
        error:function_clause ->
            false
    end.


<|start_filename|>src/optic.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A library for creating "optics", a composable traversal over
%%% arbitrary containers with the possibility of error.
%%%
%%% The traversal is modeled as an opaque type containing both a fold
%%% and mapfold type. As mapfold is a superset of fold, fold is
%%% implemented for efficiency only. The usual fold and mapfold return
%%% types are wrapped in ok/error tuples to represent the possibility
%%% of failure, with the provided compositions being responsible for
%%% propagating errors back out and skipping further execution.
%%%
%%% These optics can then be composed to read and update nested data
%%% structures. Three types of composition are possible. A wrap, which
%%% modifies an existing optic into a new form. A chain, which
%%% combines two optics so that one focuses on the value the previous
%%% focuses on. Finally, a merge, which combines two optics to allow
%%% both to focus over the same data.
%%% @end
%%%-------------------------------------------------------------------
-module(optic).

-record(optic, {fold :: optic_fold(),
                mapfold :: optic_mapfold()}).

-type error() :: {error, term()}.
%% The expected error format.
-type option(Success) :: {ok, Success} | error().
%% A result type for operations that might fail.

-type callback_map() :: fun((Elem :: term()) -> NewElem :: term()).
%% Callback function invoked by map for each element of a container.
-type callback_fold() :: fun((Elem :: term(),
                              NewAcc :: term()) ->
                             Acc :: term()).
%% Callback function invoked by fold for each element of a container.
-type callback_mapfold() :: fun((Elem :: term(),
                                 Acc :: term()) ->
                                {NewElem :: term(), NewAcc :: term()}).
%% Callback function invoked by mapfold for each element of a
%% container.
-type callback_filter() :: fun((Elem :: term()) -> boolean()).
%% Callback function invoked by filter optics for each element of a
%% container.
-type callback_new() :: fun((Data :: term(),
                             Template :: term()) ->
                         NewData :: term()).
%% Callback function invoked to create new containers.
-type optic_fold() :: fun((Fold :: callback_fold(),
                           Acc :: term(),
                           Data :: term()) ->
                          option(NewAcc :: term())).
%% A fold function to be used as part of an optic.
-type optic_mapfold() :: fun((MapFold :: callback_mapfold(),
                              Acc :: term(),
                              Data :: term()) ->
                             option({NewData :: term(), NewAcc :: term()})).
%% A mapfold function to be used as part of an optic.
-type optic_wrap(Over) :: fun((Over) -> Over).
%% A mapping function to wrap optics.
-type optic_wrap_fold() :: optic_wrap(optic_fold()).
%% A mapping function over optic folds.
-type optic_wrap_mapfold() :: optic_wrap(optic_mapfold()).
%% A mapping function over optic mapfolds.

-type variations() :: #{create=>term(),
                        strict=>boolean(),
                        filter=>callback_filter(),
                        require=>callback_filter()} | proplists:proplist().
%% Shared options to control optic construction.

-opaque optic() :: #optic{}.
%% A composable traversal over an arbitrary container.
-type optics() :: [optic()].
%% A list of traversals to be composed.

%% API
-export([% Optic creation.
         new/1,
         new/2,
         wrap/2,
         wrap/3,
         chain/1,
         merge/1,
         is_optic/1,
         variations/3,
         create/3,
         lax/1,
         % Optic application.
         fold/4,
         get/2,
         mapfold/4,
         map/3,
         put/3,
         % Optics.
         id/0,
         error/1,
         filter/1,
         require/1]).

-export_type([option/1,
              callback_map/0,
              callback_fold/0,
              callback_mapfold/0,
              callback_filter/0,
              optic_fold/0,
              optic_mapfold/0,
              optic_wrap_fold/0,
              optic_wrap_mapfold/0,
              variations/0,
              optic/0,
              optics/0]).

%%%===================================================================
%%% API - Optic Creation & Composition
%%%===================================================================

%% @doc
%% Create a new optic for traversing a data structure.
%%
%% This is the less efficient form of optic construction and will
%% infer a fold function from the given mapfold function.
%% @end
%% @returns An opaque optic record.
%% @see new/2
-spec new(MapFold) -> optic() when
      MapFold :: optic_mapfold().
new(MapFold) ->
    Fold =
    fun (Fun, Acc, Data) ->
            case MapFold(
                   fun (Elem, InnerAcc) ->
                           {Elem, Fun(Elem, InnerAcc)}
                   end,
                   Acc,
                   Data) of
                {ok, {_, NewAcc}} ->
                    {ok, NewAcc};
                {error, _} = Error ->
                    Error
            end
    end,
    new(MapFold, Fold).

%% @doc
%% Create a new optic for traversing a data structure.
%%
%% Well behaved optics should implement the following properties:
%%
%% <ul>
%% <li>Get -> Put: Writing the same value as was read should result in
%% the original structure.</li>
%% <li>Put -> Get: Reading a value that was written should result in
%% the same value as was written.</li>
%% <li>Put -> Put: Writing a value twice should result in only the
%% last written value.</li>
%% </ul>
%%
%% Optics which are not well behaved will be more difficult to use and
%% compose with other optics. Their behaviour will change depending on
%% the order in which they are applied and the number of times they
%% are applied.
%%
%% @end
%% @param MapFold
%% At a minimum, an optic requires a mapfold function to be provided
%% for both collecting and modifying values. This function must take
%% three arguments; a callback function, an initial accumulator value
%% and an arbitrary structure to traverse. The callback function will
%% expect two values; an element and the current accumulator. It will
%% return a two item tuple with the modified element and the modified
%% accumulator. The mapfold function will return a two item tuple with
%% the modified structure and the final accumulator value, wrapped in
%% an ok or error tagged tuple.
%% @end
%% @param Fold
%% A fold function can also be provided for more efficient traversal
%% without modification. If one is not provided, it will be
%% inefficiently inferred from the MapFold function. This function
%% must take three arguments; a callback function, an initial
%% accumulator value and an arbitrary structure to traverse. The
%% callback function will expect two values; an element and the
%% current accumulator. It will return the modified accumulator. The
%% fold function will return a final accumulator value, wrapped in an
%% ok or error tagged tuple.
%% @end
%% @returns An opaque optic record.
-spec new(MapFold, Fold) -> optic() when
      MapFold :: optic_mapfold(),
      Fold :: optic_fold().
new(MapFold, Fold) ->
    #optic{fold=Fold, mapfold=MapFold}.

%% @doc
%% Wrap an existing optic.
%%
%% This is the less efficient form of optic construction and will
%% infer a fold wrapper from the given mapfold wrapper.
%% @end
%% @returns An opaque optic record.
%% @see wrap/3
-spec wrap(Optic, WrapMapFold) -> optic() when
      Optic :: optic(),
      WrapMapFold :: optic_wrap_mapfold().
wrap(#optic{} = Optic, WrapMapFold) ->
    WrapFold =
    fun (Fold) ->
            MapFold = 
            fun (Fun, Acc, Data) ->
                    case Fold(Fun, Acc, Data) of
                        {ok, NewAcc} ->
                            {ok, {Data, NewAcc}};
                        {error, _} = Error ->
                            Error
                    end
            end,
            WrappedMapFold = WrapMapFold(MapFold),
            fun (Fun, Acc, Data) ->
                    case WrappedMapFold(Fun, Acc, Data) of
                        {ok, {_NewData, NewAcc}} ->
                            {ok, NewAcc};
                        {error, _} = Error ->
                            Error
                    end
            end
    end,
    wrap(Optic, WrapMapFold, WrapFold).

%% @doc
%% Wrap an existing optic.
%%
%% This allows for modifying or replacing the methods of an existing
%% optic by applying a mapping function to each of the mapfold and
%% fold methods.
%% @end
%% @param Optic An existing optic to modify.
%% @param WrapMapFold 
%% A mapping function to apply to the optic's mapfold function.
%% @end
%% @param WrapFold 
%% A mapping function to apply to the optic's fold function.
%% @end
%% @returns An opaque optic record.
-spec wrap(Optic, WrapMapFold, WrapFold) -> optic() when
      Optic :: optic(),
      WrapMapFold :: optic_wrap_mapfold(),
      WrapFold :: optic_wrap_fold().
wrap(#optic{fold=Fold, mapfold=MapFold}, WrapMapFold, WrapFold) ->
    NewMapFold = WrapMapFold(MapFold),
    NewFold = WrapFold(Fold),
    new(NewMapFold, NewFold).

%% @doc
%% Combine existing optics into a chain. In left to right order, each
%% optic then focuses on the result of the previous optic. The result
%% of this composition is itself an optic.
%%
%% This is the default composition method used for functions which
%% accept optics.
%% @end
%% @param Optics The list of optics to compose.
%% @returns An opaque optic record.
-spec chain(Optics) -> optic() when
      Optics :: optics().
chain(#optic{} = Optic) ->
    Optic;
chain([]) ->
    id();
chain([Head | Tail]) ->
    lists:foldl(fun compose/2, Head, Tail).

%% @doc
%% Merge existing optics into a single optic. In left to right order,
%% each optic focuses on the same data. The result of this composition
%% is itself an optic.
%%
%% It is the optic product type.
%% @end
%% @param Optics The list of optics to compose.
%% @returns An opaque optic record.
-spec merge(Optics) -> optic() when
      Optics :: optics().
merge(#optic{} = Optic) ->
    Optic;
merge([]) ->
    id();
merge([Head | Tail]) ->
    lists:foldl(fun product/2, Head, Tail).

%% @doc
%% Check if a term is an optic.
%% @end
%% @param Candidate The term to test.
%% @returns A boolean flag.
-spec is_optic(Candidate) -> boolean() when
      Candidate :: term().
is_optic(#optic{}) ->
    true;
is_optic(_) ->
    false.

%% @private
%% @doc
%% Internal interface for generating optics which support a range of
%% optional behaviours. The optic must return {error, undefined} when
%% it encounters an unknown type for these options to work. Due to the
%% restrictions this places on optic behaviour it is intended only for
%% internal use.
%%
%% Optics with the "create" option enabled are not well behaved, and
%% may exhibit unexpected behaviour when composed. It is also possible
%% for "filter" and "require" to no longer be well behaved, depending
%% on the filter function used.
%% @end
%% @param Optic The base optic to modify.
%% @param Options
%% The selected options. Expected options are a boolean "strict" for
%% if type errors should be reported or ignored, an arbitrarily valued
%% "create" for if type errors should force container creation, a
%% "filter" function to restrict the elements selected and a "require"
%% function to error when requirements are not met.
%% @param New
%% When the "create" option is selected, the function to invoke to
%% perform the creation.
%% @end
%% @returns An opaque optic record.
%% @see create/3
%% @see filter/1
%% @see lax/1
%% @see require/1
-spec variations(Optic, Options, New) -> optic() when
      Optic :: optic(),
      Options :: variations(),
      New :: callback_new().
variations(#optic{} = Optic, Options, New) when is_list(Options) ->
    % Normalize proplist option form to map form.
    Strict = proplists:get_bool(strict, Options),
    RequiredOptions = #{strict=>Strict},
    OptionalOptions =
    lists:foldl(
      fun (Option, Acc) ->
              case proplists:lookup(Option, Options) of
                  {Option, Value} ->
                      Acc#{Option=>Value};
                  none ->
                      Acc
              end
      end,
      RequiredOptions,
      [create, filter, require]),
    variations(Optic, OptionalOptions, New);
variations(#optic{} = Optic, #{} = Options, New) ->
    CreateOptic =
    case maps:is_key(create, Options) of
        true ->
            #{create:=Template} = Options,
            create(Optic, New, Template);
        false ->
            Optic
    end,
    LaxOptic =
    case maps:get(strict, Options, false) of
        true ->
            CreateOptic;
        false ->
            lax(CreateOptic);
        InvalidStrict ->
            erlang:error({invalid_strict_value, InvalidStrict})
    end,
    RequireOptic =
    case maps:get(require, Options, undefined) of
        undefined ->
            LaxOptic;
        Require when is_function(Require) ->
            chain([LaxOptic, require(Require)]);
        InvalidRequire ->
            erlang:error({invalid_require_value, InvalidRequire})
    end,
    FilterOptic =
    case maps:get(filter, Options, undefined) of
        undefined ->
            RequireOptic;
        Filter when is_function(Filter) ->
            chain([RequireOptic, filter(Filter)]);
        InvalidFilter ->
            erlang:error({invalid_filter_value, InvalidFilter})
    end,
    FilterOptic.

%% @doc
%% Wrap an existing optic to cause it to create a new container when
%% the optic would otherwise return `{error, undefined}' or
%% `{error, required}' during a mapfold operation.
%% @end
%% @param Optic The existing optic to wrap.
%% @param New 
%% The callback function to apply when the mapfold fails.
%% Must take two arguments, the existing data and a template argument
%% to use to populate the new data. Should return the new container,
%% which will immediately have the wrapped mapfold function re-applied
%% after creation.
%% @end
%% @param Template 
%% The template value to be given to the callback function.
%% @end
%% @returns An opaque optic record.
-spec create(Optic, New, Template) -> optic() when
      Optic :: optic(),
      New :: callback_new(),
      Template :: term().
create(Optic, New, Template) ->
    WrapFold = fun (Fold) -> Fold end,
    WrapMapFold =
    fun (MapFold) ->
            fun (Fun, Acc, Data) ->
                    case MapFold(Fun, Acc, Data) of
                        {error, Reason} when Reason == undefined;
                                             Reason == required ->
                            MapFold(Fun, Acc, New(Data, Template));
                        Result ->
                            Result
                    end
            end
    end,
    optic:wrap(Optic, WrapMapFold, WrapFold).

%% @doc
%% Wrap an existing optic to cause it to skip an element when the
%% optic would otherwise return `{error, undefined}' or 
%% `{error, required}' during a fold or mapfold operation.
%% @end
%% @param Optic The existing optic to wrap.
%% @returns An opaque optic record.
-spec lax(Optic) -> optic() when
      Optic :: optic().
lax(Optic) ->
    WrapFold =
    fun (Fold) ->
            fun (Fun, Acc, Data) ->
                    case Fold(Fun, Acc, Data) of
                        {error, Reason} when Reason == undefined;
                                             Reason == required ->
                            {ok, Acc};
                        Result ->
                            Result
                    end
            end
    end,
    WrapMapFold =
    fun (MapFold) ->
            fun (Fun, Acc, Data) ->
                    case MapFold(Fun, Acc, Data) of
                        {error, Reason} when Reason == undefined;
                                             Reason == required ->
                            {ok, {Data, Acc}};
                        Result ->
                            Result
                    end
            end
    end,
    wrap(Optic, WrapMapFold, WrapFold).

%%%===================================================================
%%% API - Optic Application
%%%===================================================================

%% @doc
%% Given a list of optics, performs a recursive fold over the result
%% of focusing on the given data structure. The order of traversal is
%% determined by the optics used.
%% @end
%% @param Optics A list of optics to apply. Leftmost is applied first.
%% @param Fold
%% The callback function to invoke on the focused elements and
%% accumulator. Expected to return the modified accumulator.
%% @end
%% @param Acc The initial accumulator value.
%% @param Data The container to apply the optics to.
%% @returns
%% On success, returns a tuple of ok and the final accumulator value.
%% On failure, returns an error tuple.
%% @end
-spec fold(Optics, Fold, Acc, Data) -> option(NewAcc) when
      Optics :: optics(),
      Fold :: callback_fold(),
      Acc :: term(),
      Data :: term(),
      NewAcc :: term().
fold(Optics, Fold, Acc, Data) ->
    #optic{fold=OpticFold} = optic:chain(Optics),
    OpticFold(Fold, Acc, Data).

%% @doc
%% Given a list of optics, returns a list of the values focused on by
%% the final optic.
%% @end
%% @param Optics A list of optics to apply. Leftmost is applied first.
%% @param Data The container to apply the optics to.
%% @returns A list of the focused values.
-spec get(Optics, Data) -> option(Values) when
      Optics :: optics(),
      Data :: term(),
      Values :: [term()].
get(Optics, Data) ->
    case fold(Optics,
              fun (Elem, Acc) -> [Elem | Acc] end,
              [],
              Data) of
        {ok, Acc} ->
            {ok, lists:reverse(Acc)};
        {error, _} = Error ->
            Error
    end.

%% @doc
%% Given a list of optics, performs a recursive map and fold over the
%% result of focusing on the given data structure.
%% @end
%% @param Optics A list of optics to apply. Leftmost is applied first.
%% @param Data The container to apply the optics to.
%% @param MapFold
%% The callback function to invoke on the focused elements and
%% accumulator. Expected to return a tuple of the modified element and
%% accumulator.
%% @end
%% @param Acc The initial accumulator value.
%% @returns
%% On success, returns a tuple of ok and a tuple of the modified
%% container and the final accumulator value. On failure, returns an
%% error tuple.
%% @end
-spec mapfold(Optics, MapFold, Acc, Data) -> option({NewData, NewAcc}) when
      Optics :: optics(),
      MapFold :: callback_mapfold(),
      Acc :: term(),
      Data :: term(),
      NewData :: term(),
      NewAcc :: term().
mapfold(Optics, MapFold, Acc, Data) ->
    #optic{mapfold=OpticMapFold} = optic:chain(Optics),
    OpticMapFold(MapFold, Acc, Data).

%% @doc
%% Given a list of optics, performs a recursive map over the result of
%% focusing on the given data structure.
%% @end
%% @param Optics A list of optics to apply. Leftmost is applied first.
%% @param Data The container to apply the optics to.
%% @param Map
%% The callback function to invoke on the focused elements. Expected
%% to return a modified element.
%% @end
%% @returns
%% On success, returns a tuple of ok and the modified container.
%% On failure, returns an error tuple.
%% @end
-spec map(Optics, Map, Data) -> option(NewData) when
      Optics :: optics(),
      Map :: callback_map(),
      Data :: term(),
      NewData :: term().
map(Optics, Map, Data) ->
    case mapfold(Optics,
                 fun (Elem, undefined) -> {Map(Elem), undefined} end,
                 undefined,
                 Data) of
        {ok, {Updated, undefined}} ->
            {ok, Updated};
        {error, _} = Error ->
            Error
    end.

%% @doc
%% Given a list of optics, modifies the values focused on by
%% the final optic.
%% @end
%% @param Optics A list of optics to apply. Leftmost is applied first.
%% @param Data The container to apply the optics to.
%% @returns
%% On success, returns a tuple of ok and the modified container.
%% On failure, returns an error tuple.
%% @end
-spec put(Optics, Value, Data) -> option(NewData) when
      Optics :: optics(),
      Value :: term(),
      Data :: term(),
      NewData :: term().
put(Optics, Value, Data) ->
    map(Optics, fun (_) -> Value end, Data).

%%%===================================================================
%%% API - Optics
%%%===================================================================

%% @doc
%% Focus on what was given.
%%
%% This is the identity optic, it can be chained with any other optic
%% and will return the same optic.
%%
%% Example:
%%
%% ```
%% > optic:get([optic:id()], anything).
%% {ok,[anything]}
%% '''
%% @end
%% @returns An opaque optic record.
-spec id() -> optic().
id() ->
    Fold =
    fun (Fun, Acc, Data) ->
            {ok, Fun(Data, Acc)}
    end,
    new(Fold, Fold).

%% @doc
%% Always errors with the given reason.
%%
%% Example:
%%
%% ```
%% > optic:get([optic:error(reason)], anything).
%% {error, reason}
%% '''
%% @end
%% @param Reason The error description to return.
%% @returns An opaque optic record.
-spec error(Reason) -> optic:optic() when
      Reason :: term().
error(Reason) ->
    Fold =
    fun (_Fun, _Acc, _Data) ->
            {error, Reason}
    end,
    new(Fold, Fold).

%% @doc
%% Only focuses on the current data if the given filter function
%% returns true. Otherwise the data is skipped.
%%
%% Can fail to be well behaved depending on if the filter criteria is
%% part of the focus.
%%
%% Example:
%%
%% ```
%% > IsOdd = fun (Elem) -> Elem % 2 == 1 end,
%% > optic:get([optic:filter(IsOdd)], [1,2,3]).
%% {ok, [1,3]}
%% '''
%% @end
%% @param Filter
%% The filter function to invoke to determine if the element should be
%% focused. Takes the current data as an argument, returns a boolean
%% true or false.
%% @end
%% @returns An opaque optic record.
-spec filter(Filter) -> optic:optic() when
      Filter :: callback_filter().
filter(Filter) ->
    Fold =
    fun (Fun, Acc, Data) ->
            case Filter(Data) of
                true ->
                    {ok, Fun(Data, Acc)};
                false ->
                    {ok, Acc}
            end
    end,
    MapFold =
    fun (Fun, Acc, Data) ->
            case Filter(Data) of
                true ->
                    {ok, Fun(Data, Acc)};
                false ->
                    {ok, {Data, Acc}}
            end
    end,
    new(MapFold, Fold).

%% @doc
%% Only focuses on the current data if the given filter function
%% returns true. Otherwise an `{error, required}` is returned.
%%
%% Can fail to be well behaved depending on if the filter criteria is
%% part of the focus.
%%
%% Example:
%%
%% ```
%% > IsOdd = fun (Elem) -> Elem % 2 == 1 end,
%% > optic:get([optic:require(IsOdd)], [1,2,3]).
%% {error, required}
%% '''
%% @end
%% @param Filter
%% The filter function to invoke to determine if the element should be
%% focused. Takes the current data as an argument, returns a boolean
%% true or false.
%% @end
%% @returns An opaque optic record.
-spec require(Filter) -> optic:optic() when
      Filter :: callback_filter().
require(Filter) ->
    Fold =
    fun (Fun, Acc, Data) ->
            case Filter(Data) of
                true ->
                    {ok, Fun(Data, Acc)};
                false ->
                    {error, required}
            end
    end,
    MapFold =
    fun (Fun, Acc, Data) ->
            case Filter(Data) of
                true ->
                    {ok, Fun(Data, Acc)};
                false ->
                    {error, required}
            end
    end,
    new(MapFold, Fold).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

compose(#optic{fold=Fold1, mapfold=MapFold1},
        #optic{fold=Fold2, mapfold=MapFold2}) ->
    Fold =
    fun (Fun, Acc, Data) ->
            case Fold2(
                   fun (Elem, {ok, InnerAcc}) ->
                           Fold1(Fun, InnerAcc, Elem);
                       (_Elem, {error, _} = Error) ->
                           Error
                   end,
                   {ok, Acc},
                   Data) of
                {ok, {ok, _} = Result} ->
                    Result;
                {ok, {error, _} = Error} ->
                    Error;
                {error, _} = Error ->
                    Error
            end
    end,
    MapFold =
    fun (Fun, Acc, Data) ->
            case MapFold2(
                   fun (Elem, {ok, InnerAcc}) ->
                           case MapFold1(Fun, InnerAcc, Elem) of
                               {ok, {NewElem, NewInnerAcc}} ->
                                   {NewElem, {ok, NewInnerAcc}};
                               {error, _} = Error ->
                                   {Elem, Error}
                           end;
                       (Elem, {error, _} = Error) ->
                           {Elem, Error}
                   end,
                   {ok, Acc},
                   Data) of
                {ok, {NewData, {ok, NewAcc}}} ->
                    {ok, {NewData, NewAcc}};
                {ok, {_, {error, _} = Error}} ->
                    Error;
                {error, _} = Error ->
                    Error
            end
    end,
    new(MapFold, Fold).

product(#optic{fold=Fold1, mapfold=MapFold1},
        #optic{fold=Fold2, mapfold=MapFold2}) ->
    Fold =
    fun (Fun, Acc, Data) ->
            case Fold2(Fun, Acc, Data) of
                {ok, NewAcc} ->
                    Fold1(Fun, NewAcc, Data);
                {error, _} = Error ->
                    Error
            end
    end,
    MapFold =
    fun (Fun, Acc, Data) ->
            case MapFold2(Fun, Acc, Data) of
                {ok, {NewData, NewAcc}} ->
                    MapFold1(Fun, NewAcc, NewData);
                {error, _} = Error ->
                    Error
            end
    end,
    new(MapFold, Fold).


<|start_filename|>include/optic_tuples.hrl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Macros for the optic_tuples module.
%%% @end
%%%-------------------------------------------------------------------

% This macro creates the first three parameters required by
% optic_tuple:field/3 for selecting the field of a record. It requires
% the record to be defined in the current module.
-define(OPTIC_FIELD(Record, Field), (Record), record_info(size, (Record)), (#Record.Field)).


<|start_filename|>test/test_optic_tuples.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_tuples.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_tuples).

-include("include/optic_tuples.hrl").
-include_lib("eunit/include/eunit.hrl").

-record(test_optic_tuples, {one=1, two=2, three=3}).

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   optic:get([optic_tuples:all([strict])], {1, 2, 3})),
     ?_assertEqual({error, undefined},
                   optic:get([optic_tuples:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, {4, 4, 4}},
                   optic:put([optic_tuples:all([strict])], 4, {1, 2, 3})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_tuples:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, {}},
                 optic:put([optic_tuples:all([strict, create])], 4, atom)).

element_get_test_() ->
    [?_assertEqual({ok, [1]},
                   optic:get([optic_tuples:element(1, [strict])], {1, 2, 3})),
     ?_assertEqual({error, undefined},
                   optic:get([optic_tuples:element(1, [strict])], atom))].

element_put_test_() ->
    [?_assertEqual({ok, {4, 2, 3}},
                   optic:put([optic_tuples:element(1, [strict])],
                             4,
                             {1, 2, 3})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_tuples:element(1, [strict])],
                             4,
                             atom))].

element_create_test_() ->
    [?_assertEqual({ok, {undefined, 4}},
                   optic:put([optic_tuples:element(2, [strict,
                                                       {create, undefined}])],
                             4,
                             atom)),
     ?_assertEqual({ok, {1, undefined, 4}},
                   optic:put([optic_tuples:element(3, [strict,
                                                       {create, undefined}])],
                             4,
                             {1}))].

field_get_test_() ->
    [?_assertEqual({ok, [1]},
                   optic:get([optic_tuples:field(
                                test_optic_tuples,
                                record_info(size, test_optic_tuples),
                                #test_optic_tuples.one,
                                [strict])],
                             #test_optic_tuples{})),
     ?_assertEqual({ok, [1]},
                   optic:get([optic_tuples:field(
                                ?OPTIC_FIELD(test_optic_tuples, one))],
                             #test_optic_tuples{})),
     ?_assertEqual({error, undefined},
                   optic:get([optic_tuples:field(
                                test_optic_tuples,
                                record_info(size, test_optic_tuples),
                                #test_optic_tuples.one,
                                [strict])],
                             {test_optic_tuples}))].

field_put_test_() ->
    [?_assertEqual({ok, {test_optic_tuples, 4, 2, 3}},
                   optic:put([optic_tuples:field(
                                test_optic_tuples,
                                record_info(size, test_optic_tuples),
                                #test_optic_tuples.one,
                                [strict])],
                             4,
                             #test_optic_tuples{})),
     ?_assertEqual({ok, {test_optic_tuples, 4, 2, 3}},
                   optic:put([optic_tuples:field(
                                ?OPTIC_FIELD(test_optic_tuples, one))],
                             4,
                             #test_optic_tuples{})),
     ?_assertEqual({error, undefined},
                   optic:put([optic_tuples:field(
                                test_optic_tuples,
                                record_info(size, test_optic_tuples),
                                #test_optic_tuples.one,
                                [strict])],
                             4,
                             {test_optic_tuples}))].

field_create_test() ->
    ?assertEqual({ok, {test_optic_tuples, 4, undefined, undefined}},
                 optic:put([optic_tuples:field(
                              test_optic_tuples,
                              record_info(size, test_optic_tuples),
                              #test_optic_tuples.one,
                              [strict, {create, undefined}])],
                           4,
                           {test_optic_tuples})).


<|start_filename|>Makefile<|end_filename|>
MAKEFLAGS += --warn-undefined-variables
.DEFAULT_GOAL := all

PYTHON := python3
REBAR ?= ${if ${wildcard ./rebar3}, ./rebar3, rebar3}

.PHONY: all
## Build everything.
all: test-unit doc
	${MAKE} compile

.PHONY: clean
## Delete intermediate files.
clean:
	${REBAR} clean -a

.PHONY: compile
## Compile all profiles.
compile:
	${REBAR} compile
	${REBAR} as test compile

.PHONY: doc
## Build documentation.
doc: require-pic2plot $(addsuffix .png,$(basename $(wildcard doc/*.pic)))
	${REBAR} edoc
	${REBAR} as markdown edoc

doc/%.png: doc/%.pic
	pic2plot --font-size=0.010 --bitmap-size=4096x4096 --line-width=0.00097656 -Tpng $< > $@

.SILENT: help
.PHONY: help
## This help screen.
help:
	# Extracts help from the Makefile itself, printing help for any rule
	# which matches the defined regular expression and that has a double
	# hash (##) comment on the line above.
	printf "Available Targets:\n\n"
	awk '/^[a-zA-Z\-\_0-9]+:/ { \
		helpMessage = match(lastLine, /^## (.*)/); \
		if (helpMessage) { \
			helpCommand = substr($$1, 0, index($$1, ":")); \
			helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
			printf "%-18s %s\n", helpCommand, helpMessage; \
		} \
	} \
	{ lastLine = $$0 }' ${MAKEFILE_LIST}

rebar.lock: rebar.config
	${REBAR} update
	${REBAR} unlock
	${REBAR} upgrade

REQUIREMENTS = $(addprefix require-,pic2plot yamllint virtualenv)

.PHONY: ${REQUIREMENTS}
${REQUIREMENTS}: what=$(patsubst require-%,%,$@)
${REQUIREMENTS}: require-%:
	@which $(what) > /dev/null || \
		(printf "%s%s%s\n" "$$(tput setaf 3)" '"$(what)" is required, please install.' "$$(tput sgr0)"; exit 1)

rebar3:
	curl -o rebar3 https://s3.amazonaws.com/rebar3/rebar3
	chmod 755 rebar3

.PHONY: test
## Run all test suites.
test: test-unit test-integration

.PHONY: test-integration
## Run the integration test suite.
test-integration:
	envsubst < config/test.config.template > config/test.config
	if ! ${REBAR} as test do ct --config=config/test.config; then \
		grep -lr 'CT Error Notification' _build/test/logs/ | xargs -n 1 html2text; \
	fi

.PHONY: test-unit
## Run the unit test suite.
test-unit: require-yamllint
	${REBAR} as prod dialyzer
	${REBAR} as test do dialyzer, eunit, proper, geas #, lint
	yamllint -s .

.virtualenv: requirements.txt
	${MAKE} require-virtualenv
	virtualenv --clear --download --always-copy -p "$$(which ${PYTHON})" .virtualenv
	.virtualenv/bin/pip install --force-reinstall --upgrade pip setuptools wheel
	.virtualenv/bin/pip install -r requirements.txt
	@printf "\n\n%s%s%s\n" "$$(tput setaf 3)" "To activate virtualenv run: source .virtualenv/bin/activate" "$$(tput sgr0)"


<|start_filename|>Dockerfile<|end_filename|>
FROM erlang:21

# Install build and test dependencies.
# python3-pip: for installing python build dependencies.
RUN set -xe && apt-get update && apt-get install -y python3-pip

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY ./requirements.txt ./
RUN pip3 install -r requirements.txt

COPY . ./
RUN make compile

ENTRYPOINT ["make"]


<|start_filename|>test/test_optic_gb_trees.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_gb_trees.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_gb_trees).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(
                     optic:get([optic_gb_trees:all([strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:all([strict])], atom))].

all_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {three, 4}, {two, 4}]},
                   sort_put(
                     optic:put([optic_gb_trees:all([strict])],
                               4,
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, []},
                 sort_put(
                   optic:put([optic_gb_trees:all([strict, create])], 4, atom))).

keys_get_test_() ->
    [?_assertEqual({ok, [one, three, two]},
                   sort_get(
                     optic:get([optic_gb_trees:keys([strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:keys([strict])], atom))].

keys_put_test_() ->
    [?_assertEqual({ok, [{four, 1}]},
                   sort_put(
                     optic:put([optic_gb_trees:keys([strict])],
                               four,
                               from_list([{one, 1}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:keys([strict])], four, atom))].

keys_create_test() ->
    ?assertEqual({ok, []},
                 sort_put(
                   optic:put([optic_gb_trees:keys([strict, create])], four, atom))).

values_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(
                     optic:get([optic_gb_trees:values([strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:values([strict])], atom))].

values_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {three, 4}, {two, 4}]},
                   sort_put(
                     optic:put([optic_gb_trees:values([strict])],
                               4,
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:values([strict])], 4, atom))].

values_create_test() ->
    ?assertEqual({ok, []},
                 sort_put(
                   optic:put([optic_gb_trees:values([strict, create])], 4, atom))).

associations_get_test_() ->
    [?_assertEqual({ok, [{one, 1}, {three, 3}, {two, 2}]},
                   sort_get(
                     optic:get([optic_gb_trees:associations([strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:associations([strict])], atom))].

associations_put_test_() ->
    [?_assertEqual({ok, [{four, 4}]},
                   sort_put(
                     optic:put([optic_gb_trees:associations([strict])],
                               {four, 4},
                               from_list([{one, 1}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:associations([strict])],
                             {four, 4},
                             atom))].

associations_create_test() ->
    ?assertEqual({ok, []},
                 sort_put(
                   optic:put([optic_gb_trees:associations([strict, create])],
                             {four, 4},
                             atom))).

key_get_test_() ->
    [?_assertEqual({ok, [1]},
                   sort_get(
                     optic:get([optic_gb_trees:key(one, [strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:key(one, [strict])], atom))].

key_put_test_() ->
    [?_assertEqual({ok, [{one, 4}, {three, 3}, {two, 2}]},
                   sort_put(
                     optic:put([optic_gb_trees:key(one, [strict])],
                               4,
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:key(one, [strict])], 4, atom))].

key_create_test_() ->
    [?_assertEqual({ok, [{four, 4}, {one, 1}]},
                   sort_put(
                     optic:put([optic_gb_trees:key(four, [strict, create])],
                               4,
                               from_list([{one, 1}])))),
     ?_assertEqual({ok, [{four, 4}]},
                   sort_put(
                     optic:put([optic_gb_trees:key(four, [strict, create])],
                               4,
                               atom)))].

association_get_test_() ->
    [?_assertEqual({ok, [{one, 1}]},
                   sort_get(
                     optic:get([optic_gb_trees:association(one, [strict])],
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_trees:association(one, [strict])], atom))].

association_put_test_() ->
    [?_assertEqual({ok, [{four, 4}, {three, 3}, {two, 2}]},
                   sort_put(
                     optic:put([optic_gb_trees:association(one, [strict])],
                               {four, 4},
                               from_list([{one, 1}, {two, 2}, {three, 3}])))),
     ?_assertEqual({ok, [{four, 4}, {three, 3}, {two, 2}]},
                   sort_put(
                     optic:put([optic_gb_trees:association(one, [strict])],
                               {four, 4},
                               from_list([{one, 1}, {two, 2}, {three, 3}, {four, clobbered}])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_trees:association(one, [strict])],
                             4,
                             atom))].

association_create_test_() ->
    [?_assertEqual({ok, [{four, 4}, {one, 1}]},
                   sort_put(
                     optic:put([optic_gb_trees:association(four, [strict, create])],
                               {four, 4},
                               from_list([{one, 1}])))),
     ?_assertEqual({ok, [{four, 4}]},
                   sort_put(
                     optic:put([optic_gb_trees:association(four, [strict, create])],
                               {four, 4},
                               atom)))].

%%%===================================================================
%%% Internal Functions
%%%===================================================================

from_list(List) ->
    Dict = orddict:from_list(List),
    gb_trees:from_orddict(Dict).

sort_get({ok, Result}) ->
    {ok, lists:sort(Result)}.

sort_put({ok, Result}) ->
    {ok, gb_trees:to_list(Result)}.


<|start_filename|>src/optic_ordsets.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% A set of optics specific to ordsets.
%%%
%%% As ordsets are internally represented as a plain list, the type
%%% checks used here are not as reliable as those used for other
%%% optics. Please ensure via other means that these optics are only
%%% used with actual ordsets.
%%% @end
%%%-------------------------------------------------------------------
-module(optic_ordsets).

%% API
-export([all/0,
         all/1]).

%%%===================================================================
%%% API
%%%===================================================================

%% @see all/1
-spec all() -> optic:optic().
all() ->
    all(#{}).

%% @doc
%% Focus on all elements of an ordset.
%%
%% Example:
%%
%% ```
%% > optic:get([optic_ordsets:all()], ordsets:from_list([1,2,3])).
%% {ok,[1,2,3]}
%% '''
%% @end
%% @param Options Common optic options.
%% @returns An opaque optic record.
-spec all(Options) -> optic:optic() when
      Options :: optic:variations().
all(Options) ->
    Fold =
    fun (Fun, Acc, Set) ->
            case is_ordset(Set) of
                true ->
                    {ok, ordsets:fold(Fun, Acc, Set)};
                false ->
                    {error, undefined}
            end
    end,
    MapFold =
    fun (Fun, Acc, Set) ->
            case is_ordset(Set) of
                true ->
                    {ok, ordsets:fold(fun (Elem, {InnerSet, InnerAcc}) ->
                                              {NewElem, NewAcc} = Fun(Elem, InnerAcc),
                                              {ordsets:add_element(NewElem, InnerSet), NewAcc}
                                      end,
                                      {ordsets:new(), Acc},
                                      Set)};
                false ->
                    {error, undefined}
            end
    end,
    New =
    fun (_Data, _Template) ->
            ordsets:new()
    end,
    Optic = optic:new(MapFold, Fold),
    optic:variations(Optic, Options, New).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

is_ordset(Unknown) when is_list(Unknown) ->
    true;
is_ordset(_Unknown) ->
    false.


<|start_filename|>doc/optic_maps.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_maps</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_maps</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
   A set of optics specific to maps.


<h2><a name="description">Description</a></h2>
   A set of optics specific to maps.
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#all-0">all/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#all-1">all/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#association-1">association/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#association-2">association/2</a></td><td>  
Focus on the association for a map key.</td></tr>
<tr><td valign="top"><a href="#associations-0">associations/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#associations-1">associations/1</a></td><td>  
Focus on all associations of a map.</td></tr>
<tr><td valign="top"><a href="#key-1">key/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#key-2">key/2</a></td><td>  
Focus on the value of a map key.</td></tr>
<tr><td valign="top"><a href="#keys-0">keys/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#keys-1">keys/1</a></td><td>  
Focus on all keys of a map.</td></tr>
<tr><td valign="top"><a href="#values-0">values/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#values-1">values/1</a></td><td>  
Focus on all values of a map.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="all-0">all/0</a></h3>
<div class="spec">
<p><tt>all() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#values-1">values/1</a>.</p>

<h3 class="function"><a name="all-1">all/1</a></h3>
<div class="spec">
<p><tt>all(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#values-1">values/1</a>.</p>

<h3 class="function"><a name="association-1">association/1</a></h3>
<div class="spec">
<p><tt>association(Key) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Key = term()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#association-2">association/2</a>.</p>

<h3 class="function"><a name="association-2">association/2</a></h3>
<div class="spec">
<p><tt>association(Key, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Key = term()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Key</tt>: The key to focus on.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the association for a map key. An association is the tuple  
of a map key and value. If the key is modified, the optic is no  
longer well behaved.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_maps:association(first)], #{first =&gt; 1, second =&gt; 2}).
  {ok,[{first,1}]}</pre></p>

<h3 class="function"><a name="associations-0">associations/0</a></h3>
<div class="spec">
<p><tt>associations() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#associations-1">associations/1</a>.</p>

<h3 class="function"><a name="associations-1">associations/1</a></h3>
<div class="spec">
<p><tt>associations(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all associations of a map. An association is a tuple of  
the key and value for each entry.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_maps:associations()], #{first =&gt; 1, second =&gt; 2}).
  {ok,[{first,1},{second,2}]}</pre></p>

<h3 class="function"><a name="key-1">key/1</a></h3>
<div class="spec">
<p><tt>key(Key) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Key = term()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#key-2">key/2</a>.</p>

<h3 class="function"><a name="key-2">key/2</a></h3>
<div class="spec">
<p><tt>key(Key, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Key = term()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Key</tt>: The key to focus on.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the value of a map key.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_maps:key(first)], #{first =&gt; 1, second =&gt; 2}).
  {ok,[1]}</pre></p>

<h3 class="function"><a name="keys-0">keys/0</a></h3>
<div class="spec">
<p><tt>keys() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#keys-1">keys/1</a>.</p>

<h3 class="function"><a name="keys-1">keys/1</a></h3>
<div class="spec">
<p><tt>keys(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all keys of a map.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_maps:keys()], #{first =&gt; 1, second =&gt; 2}).
  {ok,[first,second]}</pre></p>

<h3 class="function"><a name="values-0">values/0</a></h3>
<div class="spec">
<p><tt>values() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#values-1">values/1</a>.</p>

<h3 class="function"><a name="values-1">values/1</a></h3>
<div class="spec">
<p><tt>values(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all values of a map.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_maps:values()], #{first =&gt; 1, second =&gt; 2}).
  {ok,[1,2]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>


<|start_filename|>test/test_optic_gb_sets.erl<|end_filename|>
%%%-------------------------------------------------------------------
%%% @doc
%%% Tests for src/optic_gb_sets.erl
%%% @end
%%%-------------------------------------------------------------------
-module(test_optic_gb_sets).

-include_lib("eunit/include/eunit.hrl").

%%%===================================================================
%%% Tests
%%%===================================================================

all_get_test_() ->
    [?_assertEqual({ok, [1, 2, 3]},
                   sort_get(
                     optic:get([optic_gb_sets:all([strict])],
                               gb_sets:from_list([1, 2, 3])))),
     ?_assertEqual({error, undefined},
                   optic:get([optic_gb_sets:all([strict])], atom))].

all_map_test() ->
    ?assertEqual({ok, [2, 4, 6]},
                 sort_put(
                   optic:map([optic_gb_sets:all([strict])],
                             fun (Elem) -> Elem * 2 end,
                             gb_sets:from_list([1, 2, 3])))).

all_put_test_() ->
    [?_assertEqual({ok, [4]},
                   sort_put(
                     optic:put([optic_gb_sets:all([strict])],
                               4,
                               gb_sets:from_list([1, 2, 3])))),
     ?_assertEqual({error, undefined},
                   optic:put([optic_gb_sets:all([strict])], 4, atom))].

all_create_test() ->
    ?assertEqual({ok, gb_sets:new()},
                 optic:put([optic_gb_sets:all([strict, create])],
                           4,
                           atom)).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

sort_get({ok, Result}) ->
    {ok, lists:sort(Result)}.

sort_put({ok, Result}) ->
    {ok, lists:sort(gb_sets:to_list(Result))}.


<|start_filename|>doc/optic_lists.html<|end_filename|>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Module optic_lists</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="EDoc">
</head>
<body bgcolor="white">
<div class="navbar"><a name="#navbar_top"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<hr>

<h1>Module optic_lists</h1>
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>
   A set of optics specific to lists.


<h2><a name="description">Description</a></h2>
   A set of optics specific to lists.
<h2><a name="index">Function Index</a></h2>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#all-0">all/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#all-1">all/1</a></td><td>  
Focus on all elements of a list.</td></tr>
<tr><td valign="top"><a href="#head-0">head/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#head-1">head/1</a></td><td>  
Focus on the head of a list.</td></tr>
<tr><td valign="top"><a href="#nth-1">nth/1</a></td><td></td></tr>
<tr><td valign="top"><a href="#nth-2">nth/2</a></td><td>
  Focus on the nth element of a list.</td></tr>
<tr><td valign="top"><a href="#tail-0">tail/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#tail-1">tail/1</a></td><td>  
Focus on the tail of a list.</td></tr>
</table>

<h2><a name="functions">Function Details</a></h2>

<h3 class="function"><a name="all-0">all/0</a></h3>
<div class="spec">
<p><tt>all() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#all-1">all/1</a>.</p>

<h3 class="function"><a name="all-1">all/1</a></h3>
<div class="spec">
<p><tt>all(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on all elements of a list.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_lists:all()], [1,2,3]).
  {ok,[1,2,3]}</pre></p>

<h3 class="function"><a name="head-0">head/0</a></h3>
<div class="spec">
<p><tt>head() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#head-1">head/1</a>.</p>

<h3 class="function"><a name="head-1">head/1</a></h3>
<div class="spec">
<p><tt>head(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the head of a list. The list must have at least one  
element to have a head.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_lists:head()], [1,2,3]).
  {ok,[1]}</pre></p>

<h3 class="function"><a name="nth-1">nth/1</a></h3>
<div class="spec">
<p><tt>nth(N) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li></ul></p>
</div>
<p><b>See also:</b> <a href="#nth-2">nth/2</a>.</p>

<h3 class="function"><a name="nth-2">nth/2</a></h3>
<div class="spec">
<p><tt>nth(N, Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>N = pos_integer()</tt></li><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>N</tt>: The index of the list element to focus on.<br>
<tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>
  Focus on the nth element of a list. As with <code>lists:nth/2</code>, indexing  
begins at 1.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_lists:nth(1)], [1,2,3]).
  {ok,[1]}</pre></p>

<h3 class="function"><a name="tail-0">tail/0</a></h3>
<div class="spec">
<p><tt>tail() -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt><br></p>
</div>
<p><b>See also:</b> <a href="#tail-1">tail/1</a>.</p>

<h3 class="function"><a name="tail-1">tail/1</a></h3>
<div class="spec">
<p><tt>tail(Options) -&gt; <a href="optic.html#type-optic">optic:optic()</a></tt>
<ul class="definitions"><li><tt>Options = <a href="optic.html#type-variations">optic:variations()</a></tt></li></ul></p>
<p><tt>Options</tt>: Common optic options.<br>
</p>
<p>returns: An opaque optic record.</p>
</div><p><p>  
Focus on the tail of a list. A list must have at least one element  
to have a tail.</p>
 
  <p>Example:</p>
 
  <pre>  &gt; optic:get([optic_lists:tail()], [1,2,3]).
  {ok,[2,3]}</pre></p>
<hr>

<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc</i></p>
</body>
</html>
