【golang】22、functional options | 函数式编程、闭包

文章目录

  • 一、配置 Option
    • 1.1 options
    • 1.2 funcitonal options

一、配置 Option

1.1 options

https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html

I’ve been trying on and off to find a nice way to deal with setting options in a Go package I am writing. Options on a type, that is. The package is intricate and there will probably end up being dozens of options. There are many ways to do this kind of thing, but I wanted one that felt nice to use, didn’t require too much API (or at least not too much for the user to absorb), and could grow as needed without bloat.

I’ve tried most of the obvious ways: option structs, lots of methods, variant constructors, and more, and found them all unsatisfactory. After a bunch of trial versions over the past year or so, and a lot of conversations with other Gophers making suggestions, I’ve finally found one I like. You might like it too. Or you might not, but either way it does show an interesting use of self-referential functions.

I hope I have your attention now.

Let’s start with a simple version. We’ll refine it to get to the final version.

First, we define an option type. It is a function that takes one argument, the Foo we are operating on.

type option func(*Foo)

The idea is that an option is implemented as a function we call to set the state of that option. That may seem odd, but there’s a method in the madness.

Given the option type, we next define an Option method on *Foo that applies the options it’s passed by calling them as functions. That method is defined in the same package, say pkg, in which Foo is defined.

This is Go, so we can make the method variadic and set lots of options in a given call:

// Option sets the options specified.
func (f *Foo) Option(opts ...option) {for _, opt := range opts {opt(f)}
}

Now to provide an option, we define in pkg a function with the appropriate name and signature. Let’s say we want to control verbosity by setting an integer value stored in a field of a Foo. We provide the verbosity option by writing a function with the obvious name and have it return an option, which means a closure; inside that closure we set the field:

// Verbosity sets Foo's verbosity level to v.
func Verbosity(v int) option {return func(f *Foo) {f.verbosity = v}
}

Why return a closure instead of just doing the setting? Because we don’t want the user to have to write the closure and we want the Option method to be nice to use. (Plus there’s more to come…)

In the client of the package, we can set this option on a Foo object by writing:

foo.Option(pkg.Verbosity(3))

That’s easy and probably good enough for most purposes, but for the package I’m writing, I want to be able to use the option mechanism to set temporary values, which means it would be nice if the Option method could return the previous state. That’s easy: just save it in an empty interface value that is returned by the Option method and the underlying function type. That value flows through the code:

type option func(*Foo) interface{}// Verbosity sets Foo's verbosity level to v.
func Verbosity(v int) option {return func(f *Foo) interface{} {previous := f.verbosityf.verbosity = vreturn previous}
}// Option sets the options specified.
// It returns the previous value of the last argument.
func (f *Foo) Option(opts ...option) (previous interface{}) {for _, opt := range opts {previous = opt(f)}return previous
}

The client can use this the same as before, but if the client also wants to restore a previous value, all that’s needed is to save the return value from the first call, and then restore it.

prevVerbosity := foo.Option(pkg.Verbosity(3))
foo.DoSomeDebugging()
foo.Option(pkg.Verbosity(prevVerbosity.(int)))

The type assertion in the restoring call to Option is clumsy. We can do better if we push a little harder on our design.

First, redefine an option to be a function that sets a value and returns another option to restore the previous value.

type option func(f *Foo) option


This self-referential function definition is reminiscent of a state machine. Here we’re using it a little differently: it’s a function that returns its inverse.

Then change the return type (and meaning) of the Option method of *Foo to option from interface{}:

// Option sets the options specified.
// It returns an option to restore the last arg's previous value.
func (f *Foo) Option(opts ...option) (previous option) {for _, opt := range opts {previous = opt(f)}return previous
}

The final piece is the implementation of the actual option functions. Their inner closure must now return an option, not an interface value, and that means it must return a closure to undo itself. But that’s easy: it can just recur to prepare the closure to undo the original! It looks like this:

// Verbosity sets Foo's verbosity level to v.
func Verbosity(v int) option {return func(f *Foo) option {previous := f.verbosityf.verbosity = vreturn Verbosity(previous)}
}

Note the last line of the inner closure changed from
return previous
to
return Verbosity(previous)
Instead of just returning the old value, it now calls the surrounding function (Verbosity) to create the undo closure, and returns that closure.

Now from the client’s view this is all very nice:

prevVerbosity := foo.Option(pkg.Verbosity(3))
foo.DoSomeDebugging()
foo.Option(prevVerbosity)

And finally we take it up one more level, using Go’s defer mechanism to tidy it all up in the client:

func DoSomethingVerbosely(foo *Foo, verbosity int) {// Could combine the next two lines,// with some loss of readability.prev := foo.Option(pkg.Verbosity(verbosity))defer foo.Option(prev)// ... do some stuff with foo under high verbosity.
}

It’s worth noting that since the “verbosity” returned is now a closure, not a verbosity value, the actual previous value is hidden. If you want that value you need a little more magic, but there’s enough magic for now.

The implementation of all this may seem like overkill but it’s actually just a few lines for each option, and has great generality. Most important, it’s really nice to use from the point of view of the package’s client. I’m finally happy with the design. I’m also happy at the way this uses Go’s closures to achieve its goals with grace.

1.2 funcitonal options

https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/657263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学公式OCR识别php 对接mathpix api 使用公式编译器

数学公式OCR识别php 对接mathpix api 一、注册账号官网网址:https://mathpix.com 二、该产品支持多端使用注意说明(每月10次) 三、api 对接第一步创建create keyphp对接api这里先封装两个请求函数,get 和post ,通过官方…

20240130在ubuntu20.04.6下卸载NVIDIA显卡的驱动

20240130在ubuntu20.04.6下卸载NVIDIA显卡的驱动 2024/1/30 12:58 缘起,为了在ubuntu20.4.6下使用whisper,以前用的是GTX1080M,装了535的驱动。 现在在PDD拼多多上了入手了一张二手的GTX1080,需要将安装最新的545的驱动程序&#…

VxTerm:SSH工具中的中文显示和乱码时的相关信息和一些基本的知识

当我们写的程序含有控制台(Console)输出时,如果输入内容包含中文时,我们一般需要知道下面的信息,才能正确的搞清楚怎么处理中文显示的问题: 1、实际程序或文件中的实际编码: Linux下的应用程序和文本文件,…

CVE-2024-0352 likeshop v2.5.7文件上传漏洞分析

本次的漏洞研究基于thinkPHP开发开的一款项目..... 漏洞描述 Likeshop是Likeshop开源的一个社交商务策略的完整解决方案,开源免费版基于thinkPHP开发。Likeshop 2.5.7.20210311及之前版本存在代码问题漏洞,该漏洞源于文件server/application/api/contr…

云原生Kubernetes: Ubuntu 安装 K8S 1.23版本(单Master架构) 及故障恢复

目录 一、实验 1.环境 2.安装 Ubuntu 3.连接Ubuntu 4.master节点安装docker 5.node节点安装docker 6.master节点安装K8S 7.添加K8S工作节点 8.安装网络插件calico 9.故障 10.故障恢复 11.测试k8s网络和coredns 二、问题 1.Ubuntu如何修改镜像源 2.Ubuntu和Windo…

DataTable.Load(reader)注意事项

对于在C#中操作数据库查询,这样的代码很常见: using var cmd ExecuteCommand(sql); using var reader cmd.ExecuteReader(); DataTable dt new DataTable(); dt.Load(reader); ...一般的查询是没问题的,但是如果涉及主键列的查询&#xf…

交叉注意力融合时域、频域特征的FFT + CNN-Transformer-CrossAttention轴承故障识别模型

目录 往期精彩内容: 前言 1 快速傅里叶变换FFT原理介绍 第一步,导入部分数据 第二步,故障信号可视化 第三步,故障信号经过FFT可视化 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 3 交叉注意力机制 …

网站地址怎么改成HTTPS?

现在,所有类型的网站都需要通过 HTTPS 协议进行安全连接,而实现这一目标的唯一方法是使用 SSL 证书。如果您不将 HTTP 转换为 HTTPS,浏览器和应用程序会将您网站的连接标记为不安全。 但用户询问如何将我的网站从 HTTP 更改为 HTTPS。在此页…

移动端设计规范 - 文字使用规范

这是一篇关于移动端产品界面设计时,文字大小的使用规范,前端人员如果能了解一点的话,在实际开发中和设计沟通时,节省沟通成本,也能提高设计落地开发时的还原度。 关于 在做移动端产品设计时,有时候使用文字…

【开源精选导航】GitHub-Chinese-Top-Charts:一榜在手,优质中文项目轻松找寻

各位热爱开源技术的朋友们,你们是否有过这样的困扰:面对浩瀚的GitHub海洋,想找寻那些具有高质量中文文档的优秀开源项目却无从下手?今天,我们就为大家揭晓一个宝藏般的开源项目——GitHub 中文项目集合(访问…

二维数组移动,合并数值简易2048

2848简易核心运算 --多元素合并数组举例4*4 -- 星空露珠韩永旗制作 --数据合并并重新赋值 --多元素合并数组举例4*4 -- 星空露珠韩永旗制作 --数据合并并重新赋值 local data{{0,2,0,2}, {4,2,0,8}, {8,0,8,4}, {2,2,4,8}} local ch{{1…

【Node.js基础】Node.js的介绍与安装

文章目录 前言一、什么是Node.js?二、安装Node.js2.1 Windows系统2.2 macOS系统2.3 Linux系统 三、运行js代码总结 前言 随着互联网技术的不断发展,构建高性能、实时应用的需求日益增长。Node.js作为一种服务器端运行时环境,以其事件驱动、非…

万户 ezOFFICE SendFileCheckTemplateEdit.jsp SQL注入漏洞

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

从比亚迪的整车智能战略,看王传福的前瞻市场布局

众所周知,作为中国新能源汽车的代表企业,比亚迪在中国乃至全球的新能源汽车市场一直都扮演着引领者的角色。2024年新年伊始,比亚迪又为新能源汽车带来了一项重磅发布。 整车智能才是真智能 近日,在“2024比亚迪梦想日”上&#xf…

基于二值化图像转GCode的斜向扫描实现

基于二值化图像转GCode的斜向扫描实现 什么是斜向扫描斜向扫描代码示例 基于二值化图像转GCode的斜向扫描实现 什么是斜向扫描 在激光雕刻中,斜向扫描(Diagonal Scanning)是一种雕刻技术,其中激光头沿着对角线方向来回移动&…

编译opencv4.6问题汇总,第三方软件包见我发的资源

win10系统 python3.8.2,cmake-3.15.5-win64-x64,opencv4.6 编译方式见:OpenCV的编译 - 知乎 本文主要总结问题。赠人玫瑰手留余香。 问题1 Problem with installing OpenCV using Visual Studio and CMake (error code: MSB3073) 解决方法…

c# textbox 提示文字

1. 定义提示文字内容 private readonly string RemarkText "最多输入100字"; // 提示文字 2. 添加textbox 焦点事件, 初始化textbox提示文字和字体颜色 public UserControl(){InitializeComponent();tb_Remark.Text RemarkText;tb_Remark.ForeColor…

微信小程序(二十六)列表渲染基础核心

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.列表渲染基础写法 2.外部索引和自身索引 源码&#xff1a; index.wxml <view class"students"><view class"item"><text>序号</text><text>姓名</text…

字符串中的单词反转【leetcode】

本题选自leetcode图解算法数据结构一书 你在与一位习惯从右往左阅读的朋友发消息&#xff0c;他发出的文字顺序都与正常相反但单词内容正确&#xff0c;为了和他顺利交流你决定写一个转换程序&#xff0c;把他所发的消息 message 转换为正常语序。 注意&#xff1a;输入字符串…

走迷宫-bfs

package Test;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class Main {static int N 110,hh 0,tt -1,n,m;static int[][] g new int[N][N]; //用来存储迷宫static int[][] d new int[N][N]; //用来存储d[i…