Using Rob Pike's self referential functions design in a rest client

blov · · 494 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Rob Pike wrote a cool article a while back talking about self referential functions: <a href="https://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html">https://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html</a></p> <p>It&#39;s a pretty cool design pattern that helps make your Go package APIs easy to consume, yet still be highly flexible. I figured a rest client was a good candidate for putting this pattern into practice. </p> <p>You can check out that repo here: <a href="https://github.com/zpatrick/rclient">https://github.com/zpatrick/rclient</a></p> <p>Let me know what you think!</p> <hr/>**评论:**<br/><br/>JackOhBlades: <pre><p>Correct me if I&#39;m wrong, but isn&#39;t this more commonly known as the &#34;functional options pattern&#34;?</p> <p><a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis">https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis</a></p> <p><a href="http://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/">http://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/</a></p></pre>sthussey: <pre><p>Dave Cheney&#39;s article references Pike&#39;s as inspiration, so yes.</p></pre>parnmatt: <pre><p>This is different but similar to Dave Cheney&#39;s functional options. </p> <p>Dave writes a function that operates on an object and returns an object of the same type with the option the function changes. </p> <p>Rob has a method on the object that will be changed, that takes a function to do the change, (and also return a function to reverse it)</p></pre>j_d_q: <pre><p><a href="https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html">Working link to the blog post</a></p> <p>On mobile, the Reddit app doesn&#39;t let me change the URL and I couldn&#39;t remove the last <code>.</code> from OP&#39;s link</p> <p>I like the pattern, but I&#39;m curious why he decided against just using a copy and returning the new version (with the new value). I understand for memory reasons/places you&#39;d be instantiating often, but I&#39;d mostly use this for setup type work (server, handler, etc). Are there downsides to this that I&#39;m overlooking?</p></pre>zpatrick319: <pre><p>I&#39;m not sure what the benefits of that are? </p> <p>It really depends how you create the copy. It becomes a little bit trickier when the struct has private fields. This is easily done if you create the self-referential functions inside of the package. However, self-referential functions outside of the package would need to either use a helper function, or the constructor would need to generate the copy before sending it off to the self-referential function. But - both options are pretty quick to implement all things considered. </p> <p>Also, you could potentially get into odd territory if the struct has pointer fields. Do you do a shallow copy, deep copy, etc.? </p></pre>j_d_q: <pre><p>I mean in the sense of automatic copies with <code>Opt(Foo) Foo</code> instead of <code>Opt(*Foo) Option</code></p> <p>This takes a foo, returns a new one. No need for a function to return it to previous state -- just use the foo you passed in</p> <p>As for pointer fields (and slices) -- I&#39;d leave it up to the implementation / suggest toward how the language would naturally treat it</p> <p>EDIT: Places I&#39;d find it useful to mutate: when your object reference is passed down and you need to change it dynamically (set log level of a long lived struct)</p> <p>Places I&#39;m questioning: setting up an item before passing it down (requires auth, with timeout, log errors, set header) -- maybe more of middleware concerns</p></pre>Sphax: <pre><p>Nice ! I like it. I don&#39;t think I&#39;ve seen this blog post from Rob Pike before, it&#39;s a nice pattern.</p></pre>bigpigfoot: <pre><p>thats an interesting pattern. i hope i come across a problem where this would fit.</p></pre>ar1819: <pre><p>I don&#39;t think that returning error from functional option is a good idea because it would imply that this function has state. </p> <p>Also the pattern is interesting and very elegant in some situations, but don&#39;t take it to the maximum. IMHO it should only be used before it becomes overly verbose. </p></pre>barsonme: <pre><p>otoh, using Rob&#39;s example, let&#39;s say your verbosity level should be in [0, 3]. What if the user calls Option(pkg.Verbosity(42))?</p> <p>That&#39;s a simple example, but there&#39;s definitely other more complex examples.</p></pre>ar1819: <pre><p>This is usually handled using custom type based on int and const variables. It doesn&#39;t solve the problem of arbitrary input because untyped numbers will be implicitly converted to the correct type, but it will allow you to express your intentions more precisely. </p> <p>As for function parameters validation, using Rob&#39;s pattern one can create functional option which would return another function that would set object state to invalid and those propagate error. </p></pre>barsonme: <pre><p>IMO it seems kinda gross to initialize an object and then have it return an error on first use, which is why I&#39;m not entirely opposed to a functional option returning an error. Not only does it delay the issue a bit (invalid instantiation) but it also requires the object to store an error and also requires the object&#39;s methods to have error returns to return a propagated error.</p></pre>ar1819: <pre><p>You could always return pair of option, error from the function that creates the option closure. That way you will also enforce the handling of invalid inputs, but for a price of fluent object creation. </p> <p>I don&#39;t think that verifying arguments inside functional option is wise. Mainly because it became non obvious how to handle &#34;partially applied&#34; options if error had happened. You could resort to tricks with defer and essentially rollback entire option apply, but what kind of previous option you should return then? There are also problems when several options has been passed to the object builder and one of them had returned an error - in that case you left with partially initialized object or no object at all. </p> <p>Enforce your contracts at the borders, not inside of your app/lib domain. </p></pre>barsonme: <pre><p>I&#39;d be fine with something like this, but only if errors are needed as to not allow an invalid object to be created. The alternative is either ignoring errors (i.e., <code>if v &gt; 3 { v = 3 }</code>) or having to wait until the next method call on <code>T</code> to get the error value. (Which requires propagating errors like bufio.Scanner and sql.Rows do.)</p> <pre><code>type Option func(t *T) (Option, error) func Verbosity(v int) (Option, error) { if v &gt; 3 { return nil, errors.New( ... ) } return func(t *T) (Option, error) { prev := t.v t.v = v return Verbosity(prev) }, nil } </code></pre></pre>ar1819: <pre><p>Your option type doesn&#39;t need to return error because previous state is already &#34;valid&#34;. You only want to constraint new inputs. So essentially - only Verbosity should verify input and than call some another function (possibly private) which will return the correct closure. I will show the example as soon as I get to my PC. </p> <p>Keep in mind that objects creators that receive functional options should also take into consideration that there can be zero functional options passed to them. If you want to enforce that some fields are set - its better to explicitly require them on creation. </p></pre>barsonme: <pre><blockquote> <p>Your option type doesn&#39;t need to return error because previous state is already &#34;valid&#34;. </p> </blockquote> <p>I&#39;m not following. If somebody tries to update the object with an invalid state it should be rejected.</p></pre>exch: <pre><p>I would also want to know if my new state was applied or not. Just silently ignoring it seems problematic at best.</p></pre>epiris: <pre><p>The general pattern I follow and I&#39;m sure would apply here is like bufio.Scanner, have options write to private err field and add a Err() method. If the struct that your mutating is not the place that it&#39;s used propagate it where it&#39;s accepted. This type API is fair enough in places like config setup I think since its more or less a single operation.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

494 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传