<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Sat, Oct 1, 2016 at 10:36 PM, Lloyd R. Prentice <span dir="ltr"><<a href="mailto:lloyd@writersglen.com" target="_blank">lloyd@writersglen.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Can you please explain this. </blockquote></div><br>In languages such as OCaml, you can define algebraic datatypes which encode certain invariants of your program. By doing so, you can sometimes build your construction such that illegal states cannot be represented. This "make the illegal states impossible" approach has been handled by Yaron Minsky, among others. Concretely, a good way of defining the state of a file is:</div><div class="gmail_extra"><br></div><div class="gmail_extra">type file_state = Closed | Open of (char Stream.t)</div><div class="gmail_extra"><br></div><div class="gmail_extra">In Erlang, we would write:</div><div class="gmail_extra"><br></div><div class="gmail_extra">-type file_state() = closed | {open, port()}</div><div class="gmail_extra"><br></div><div class="gmail_extra">but the gist is the same thing. When we match on the file_state, the interesting thing happens:</div><div class="gmail_extra"><br></div><div class="gmail_extra">let barney fs =</div><div class="gmail_extra">    match fs with</div><div class="gmail_extra">    | Closed -> ...</div><div class="gmail_extra">    | Open stream -></div><div class="gmail_extra">        ...</div><div class="gmail_extra"><br></div><div class="gmail_extra">Note how we only have access to the 'stream' component when the file is open, but not when it is closed. If we manage this invariant in the code, there is no way to accidentally sit with a closed file descriptor[0]. In Erlang, we can get much the same flow:</div><div class="gmail_extra"><br></div><div class="gmail_extra">barney(.., closed) -> ...;</div><div class="gmail_extra">barney(.., {open, Port}) -></div><div class="gmail_extra">    ...</div><div class="gmail_extra"><br></div><div class="gmail_extra">Note again how the only way to get to the port field is when we have an open port. The--admittedly contrived--naive approach is to write:</div><div class="gmail_extra"><br></div><div class="gmail_extra">-record(state, {</div><div class="gmail_extra">    status = closed :: closed | open,</div><div class="gmail_extra">    port = undefined :: undefined | port()</div><div class="gmail_extra">}).</div><div class="gmail_extra"><br></div><div class="gmail_extra">But note that we can thus write:</div><div class="gmail_extra"><br></div><div class="gmail_extra">State1 = #state{ status = closed, port = undefined },</div><div class="gmail_extra">State2 = #state{ status = open, port = Port },</div><div class="gmail_extra">State3 = #state{ status = closed, port = Port },</div><div class="gmail_extra">State4 = #state{ status = open, port = undefined }</div><div class="gmail_extra"><br></div><div class="gmail_extra">Here states 1 and 2 are valid states, but 3 and 4 are not. Yet our record allows for their representation! It is a common mistake in programming to write down such illegal states[1], and by alluding to the algebraic datatype, you can avoid them. The key idea is to encode your state as a term which has no extra information at any point, but is precise as to what data/information you have at a given point in time.</div><div class="gmail_extra"><br></div><div class="gmail_extra">For a real-world example, see <a href="https://github.com/shopgun/turtle" target="_blank">https://github.com/shopgun/<wbr>turtle</a> , in which this technique is used in a couple of places. Turtle is a wrapper for RabbitMQ making the official driver a bit more OTP-like. In RabbitMQ (AMQP), you first open a connection and draw channels inside the connection. Communication happens on a certain channel, not on the connection. In order to handle connections as Fred Hebert writes in his "Its about the guarantees" post[2], we want to start up processes in a known state, and then switch their internals once we have a valid connection.</div><div class="gmail_extra"><br></div><div class="gmail_extra">In particular, if you want to publish to RabbitMQ, you want to add a publisher process to your own supervision tree. This process will have to wait until a connection is established to RabbitMQ and then it will need to draw the channel and connect. The publisher is a gen_server and its Module:init/1 callback is:</div><div class="gmail_extra"><br></div><div class="gmail_extra"><a href="https://github.com/shopgun/turtle/blob/401aea5dc13256f1ed5fbf70830e86153a4db740/src/turtle_publisher.erl#L151-L155" target="_blank">https://github.com/shopgun/<wbr>turtle/blob/<wbr>401aea5dc13256f1ed5fbf70830e86<wbr>153a4db740/src/turtle_<wbr>publisher.erl#L151-L155</a><br></div><div class="gmail_extra"></div><div class="gmail_extra"><br></div><div class="gmail_extra"><div class="gmail_extra">init([{takeover, Name}, ConnName, Options]) -></div><div class="gmail_extra">    process_flag(trap_exit, true),</div><div class="gmail_extra">    Ref = gproc:nb_wait({n,l,{turtle,<wbr>connection, ConnName}}),</div><div class="gmail_extra">    ok = exometer:ensure([ConnName, Name, casts], spiral, []),</div><div class="gmail_extra">    {ok, {initializing_takeover, Name, Ref, ConnName, Options}};</div><div class="gmail_extra"><br></div><div class="gmail_extra">The process_flag/2 is for handling the fact the official driver cannot close down appropriately. By writing the publisher with trap_exit, we can protect the rest of the Erlang system against its misbehavior. We set up gproc to tell us when there is a connection ready. Then we tell exometer to create a spiral so we can track the behavior of the publisher in our metrics solution. Finally, we get into the "initializing" state. Note how we don't use the "real" state here. Then later on in the file, we handle the message from grpoc:</div><div class="gmail_extra"><br></div><div class="gmail_extra"><a href="https://github.com/shopgun/turtle/blob/401aea5dc13256f1ed5fbf70830e86153a4db740/src/turtle_publisher.erl#L210-L230">https://github.com/shopgun/turtle/blob/401aea5dc13256f1ed5fbf70830e86153a4db740/src/turtle_publisher.erl#L210-L230</a><br></div><div class="gmail_extra"><br></div><div class="gmail_extra"><div class="gmail_extra">handle_info({gproc, Ref, registered, {_, Pid, _}}, {initializing, N, Ref, CName, Options}) -></div><div class="gmail_extra">    {ok, Channel} = turtle:open_channel(CName),</div><div class="gmail_extra">    #{ declarations := Decls, passive := Passive, confirms := Confirms} = Options,</div><div class="gmail_extra">    ok = turtle:declare(Channel, Decls, #{ passive => Passive }),</div><div class="gmail_extra">    ok = turtle:qos(Channel, Options),</div><div class="gmail_extra">    ok = handle_confirms(Channel, Options),</div><div class="gmail_extra">    {ok, ReplyQueue, Tag} = handle_rpc(Channel, Options),</div><div class="gmail_extra">    ConnMRef = monitor(process, Pid),</div><div class="gmail_extra">    ChanMRef = monitor(process, Channel),</div><div class="gmail_extra">    reg(N),</div><div class="gmail_extra">    {noreply,</div><div class="gmail_extra">      #state {</div><div class="gmail_extra">        channel = Channel,</div><div class="gmail_extra">        channel_ref = ChanMRef,</div><div class="gmail_extra">        conn_ref = ConnMRef,</div><div class="gmail_extra">        conn_name = CName,</div><div class="gmail_extra">        confirms = Confirms,</div><div class="gmail_extra">        corr_id = 0,</div><div class="gmail_extra">        reply_queue = ReplyQueue,</div><div class="gmail_extra">        consumer_tag = Tag,</div><div class="gmail_extra">        name = N}};</div><div class="gmail_extra"><br></div><div class="gmail_extra">When we have a valid connection, gproc tells us. And we are in the initializing state. So we then set up the fabric in RabbitMQ, set appropriate monitors, register ourselves and then build up the "real" state record for the process.</div><div class="gmail_extra"><br></div><div class="gmail_extra">The idea here is that we have a special state for when we are initializing which is different from the normal operating state in the system. This avoids having to populate our #state{} record with lots of "undefined" values. In turn, we can define the type scheme of the #state{} record more precisely because when we initialize it, every value is a valid value. Now the dialyzer becomes far more powerful because it has a simpler record to work with type-wise.</div><div class="gmail_extra"><br></div><div class="gmail_extra">The original question was about maps. Since maps are dynamic in nature, you can sometimes use them by avoiding to populate fields before they are available and have valid data in them. This gives you the same structure as above, albeit simpler. You could encode the closed state as</div><div class="gmail_extra"><br></div><div class="gmail_extra">State1 = #{}</div><div class="gmail_extra"><br></div><div class="gmail_extra">and the open state as</div><div class="gmail_extra"><br></div><div class="gmail_extra">State2 = #{ port => Port }</div><div class="gmail_extra"><br></div><div class="gmail_extra">Now, any match which needs the port must match on it:</div><div class="gmail_extra"><br></div><div class="gmail_extra">case State of</div><div class="gmail_extra">    #{ port := Port } -> ...</div><div class="gmail_extra">end,</div><div class="gmail_extra"><br></div><div class="gmail_extra">so you cannot by accident have an uninitialized port. In Erlang/OTP Release 19.x we even have the dialyzer able to work with maps, so we can type this as well and get the dialyzer to figure out where there are problems in the code base.</div></div></div><div class="gmail_extra"><br></div><div class="gmail_extra">The method is not nearly as powerful as it is in OCaml. Static type systems can tell you, at compile time, where your constructions are wrong. With a bit more type-level work, you can encode even more invariants. For instance, you can discriminate a public key from a private key in a public-key cryptosystem, without ever having a runtime overhead of doing so.</div><div class="gmail_extra"><br></div><div class="gmail_extra">[0] Of course, this is slightly false. Network sockets may close for other reasons for instance.<br></div><div class="gmail_extra"><br></div><div class="gmail_extra">[1] The Go programming language is notorious for doing this. Either by returning a pair (result, error) where the error if nil if the result is valid and vice versa. Or by using a struct in which certain fields encode if other fields are valid.<br clear="all"><div><br></div><div>[2] <a href="http://ferd.ca/it-s-about-the-guarantees.html" target="_blank">http://ferd.ca/it-s-about-<wbr>the-guarantees.html</a></div><div><br></div>-- <br><div class="gmail-m_7101931096604537643gmail_signature">J.</div>
</div></div>