{"id":28829637,"url":"https://github.com/client9/semi-functional-options","last_synced_at":"2026-01-31T22:34:20.140Z","repository":{"id":298923893,"uuid":"1001129882","full_name":"client9/semi-functional-options","owner":"client9","description":"A different pattern for functional configuration","archived":false,"fork":false,"pushed_at":"2025-06-13T15:49:42.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-13T16:57:36.611Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/client9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-12T21:47:51.000Z","updated_at":"2025-06-13T15:49:46.000Z","dependencies_parsed_at":"2025-06-13T16:57:38.365Z","dependency_job_id":"c14eca31-6a95-4ce8-b9f3-d1f0eb9df37a","html_url":"https://github.com/client9/semi-functional-options","commit_stats":null,"previous_names":["client9/semi-functional-options"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/client9/semi-functional-options","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/client9%2Fsemi-functional-options","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/client9%2Fsemi-functional-options/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/client9%2Fsemi-functional-options/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/client9%2Fsemi-functional-options/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/client9","download_url":"https://codeload.github.com/client9/semi-functional-options/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/client9%2Fsemi-functional-options/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260691057,"owners_count":23047109,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-19T05:14:23.821Z","updated_at":"2026-01-31T22:34:20.135Z","avatar_url":"https://github.com/client9.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# semi-functional-options\nA different pattern for functional configuration in golang\n\nDRAFT.\n\nNote: maybe this should be called \"better functional variadic configurational\"  Yuck.\n\nNote: skip debate on whether functional configuration is a good idea as compared to alternatives.\n\nNote: https://www.youtube.com/watch?v=5uM6z7RnReE\u0026t=1035s\n\n# The OG Functional Options\n\nYou've seen this:\n\nhttps://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis\n\nwhich was based on\n\nhttps://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html\n\nFantastic.  Except.. \n\n* How do you share options between packages? \n* How do you take a configuration and turn it back into the functional args that created it?\n* How do you check that one configuration is the same as another?\n* How do you merge two configurations?\n* How do you save a configuration?\n\n* It's a lot of code! Well not really but it's 3 - 4 lines of code that are all identical.  Someone smart should write some type of generator using type annotations (similar to JSON).\n* Options are tied to a specific configuration structure.  This makes it hard to share options.  One can probably figure out some clever way aroudn this using embedded structs, but then it's More Code!\n\nMany of these points could be solved with More Code!\n\nThese points might not matter at all in a small project.\n\n## dsnet's Crazy Experiment in JSON2\n\nSo, Golang's JSON functionality needs a rewrite!  \n\nhttps://github.com/golang/go/discussions/63397\n\nThe proposal uses functional options, which would be a first in the golang Standard Library.  But they work differently.  Very differently.\n\nSome context:\n\ndsnet works at Tailscale which making networking stuff that runs in very contrained devices (think: VPN on your phone). Im surprised golang works at all in these environments!\n\nFirst some design goals (I'm inferring)\n\n* No Reflection (Using reflect dramatically changes the output of the final binary).\n* No Heap\n* Maximum performance.\n* Next to mix and match parts of the functionality\n* Need to be able to provide old broken v1 functionality.\n\nThe first three are certainly satified with a struct.  The last two are what pushed the design to functional options.\n\n\"\"\"\nIf v1 did not exist, then I would argue for the use of configuration structs.\n\"\"\"\n\n\nSolution:\n\nThe version in JSON2 is some work to read since it's split into multiple packages, and many of the options are implimented as flags.\n\nBut a simplified (and incomplete) version is:\n\n```go\n\n// here's your private/internal configuration struct\ntype config struct {\n        indent string\n}\n\n// every option needs to impliment this signature\n// so we can identify them\ntype Option interface {\n  function bogus()\n}\n\n// every option has it's own type!\ntype Indent string\n\n// that implements the Option interface\nfunc (Indent) bogus() {}\n\n// here's the functional option\nfunc WithIndent(s string) Option {\n   return Indent(s)\n}\n```\n\nNote:\n* Every option has a unique `type`\n* Every unqiue type impliments the `Option` interface using a bogus method.\n\n`WithIndent` returns a `Indent(string)`.  Very clever.\n\nBut given a list of Options how do you translate it back to a configuration struct?\n \nA simplified version of looking up an option.\n\n```go\n// to turn the options back into config struct\n// use a type switch and cast.\nfunc collectOptions(c *config, opts []Option) {\n    for _, opt := range opts {\n        switch val := opt.(type) {\n\n        // For each option...\n        // Explicity set the internal configuration\n        case Indent:\n            c.indent = string(val)\n   }\n}\n```\n\n\nWe replaced the function closure with a custom type.\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclient9%2Fsemi-functional-options","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclient9%2Fsemi-functional-options","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclient9%2Fsemi-functional-options/lists"}