{"id":22813343,"url":"https://github.com/southclaws/opt","last_synced_at":"2025-04-22T16:54:16.409Z","repository":{"id":61628521,"uuid":"550256016","full_name":"Southclaws/opt","owner":"Southclaws","description":"A simple and ergonomic optional type for Go.","archived":false,"fork":false,"pushed_at":"2024-01-30T14:39:33.000Z","size":29,"stargazers_count":35,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-29T16:51:13.481Z","etag":null,"topics":["functional-programming","go","golang","maybe-type","optional","optional-type"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Southclaws.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}},"created_at":"2022-10-12T13:02:07.000Z","updated_at":"2024-08-14T05:05:28.000Z","dependencies_parsed_at":"2024-06-20T11:14:43.133Z","dependency_job_id":"86013203-ce1e-4915-81a7-f2658dc03a1f","html_url":"https://github.com/Southclaws/opt","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Southclaws%2Fopt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Southclaws%2Fopt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Southclaws%2Fopt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Southclaws%2Fopt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Southclaws","download_url":"https://codeload.github.com/Southclaws/opt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250283485,"owners_count":21405172,"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":["functional-programming","go","golang","maybe-type","optional","optional-type"],"created_at":"2024-12-12T12:26:57.639Z","updated_at":"2025-04-22T16:54:16.368Z","avatar_url":"https://github.com/Southclaws.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# opt\n\n\u003e Optional types and utilities for egonomic data transformation.\n\n[![GoDoc](https://pkg.go.dev/badge/github.com/Southclaws/opt)](https://pkg.go.dev/github.com/Southclaws/opt?tab=doc)\n[![Go Report Card](https://goreportcard.com/badge/github.com/Southclaws/opt)](https://goreportcard.com/report/github.com/Southclaws/opt)\n\nopt provides a simple generic optional type with a variety of utilities for\nperforming various transformations without the need for explicit branching.\n\nSo, while there are many `Optional[T]` style packages out there, this one has a\nfocus on making _data transformations_ easier to write and easier to read.\n\nIt also prevents certain categories of bug such as nil pointer dereferencing. Of\ncourse this comes at a cost and if you're writing performance sensitive code,\nthis library may not be for you and you may be better off just being explicit.\n\nThe status of this library is pre-1.0 but the API is stable and probably won't\nchange. It has been dogfooded in 3 production codebases for about a year and all\nAPIs were built to solve some real problems in those projects.\n\n## Basics\n\nLet's get the obvious out of the way first...\n\n```go\nfunc main() {\n    maybe := opt.New(\"I exist!\")\n\n    maybe.Ok() // true\n    value, exists := maybe.Get() // \"I exist!\", true\n    ptr := maybe.Ptr() // some address\n\n    maybe_not := opt.NewEmpty[string]()\n\n    maybe_not.Ok() // false\n    value, exists := maybe_not.Get() // \"\", false\n    ptr := maybe_not.Ptr() // nil\n}\n```\n\nOptional, generic, yada yada, whatever. Every other optional package does it.\n\nThe interesting parts are the construction, mapping and access utilities...\n\n## Accessing\n\nOnce you've constructed an optional value, you can access the underlying data in\na few ways. These make it easy to build branching logic without the need for\nexplicit if statements. Which can be useful for transforming large structures.\n\nThe simplest ones are `Ok` and `Get` which have examples above. See the GoDoc\nfor more info on these, they're fairly simple and do what you'd expect.\n\nOne method that isn't mentioned above is `Call`. Which simply lets you call a\nfunction with the value if it's present:\n\n```go\nmaybe := opt.New(\"I exist!\")\nmaybe.Call(func(value string) {\n    fmt.Println(value)\n})\n```\n\nThese have been handy for some ORM setter APIs:\n\n```go\nemail.Call(accountQuery.SetEmailAddress)\n```\n\n## Mapping\n\nOne of the core reasons this library was written was to facilitate easy mapping\nof data types that may or may not be present. Without the need for code that\nlooks like this:\n\n```go\nvar newValue *T\nif oldValue != nil {\n    newValue = transform(*oldValue)\n}\n```\n\nWhich is fine on its own, but if you have many values, it can get quite verbose.\n\nopt instead provides a way to map data as an access or map data as a pipeline.\nTo access the data, you already know about `Get` but if you want to change the\ntype at the same time as accessing, you can use `GetMap`:\n\n```go\nmaybe := opt.New(\"I exist!\")\nvalue, exists := opt.GetMap(maybe, strings.ToUpper)\n// \"I EXIST!\", true\n```\n\nIf your destination is expecting a pointer, you can use `PtrMap`:\n\n```go\nmaybe := opt.New(\"I exist!\")\nvalue := opt.PtrMap(maybe, strings.ToUpper)\n// \"I EXIST!\" as a `*string`\n```\n\nNote how these are functions of the library, not methods on the type. It would\nbe nice to be able to write `maybe.PtrMap(strings.ToUpper)` but currently, this\nis not possible to do in the current version of Go's generics.\n\nIf you want to transform the data but keep it wrapped as an optional type, you\ncan use `Map` or `MapErr` to execute the closure, only if the value is present:\n\n```go\nmaybe := opt.New(\"I exist!\")\nmaybe = opt.Map(maybe, strings.ToUpper)\n// opt.Optional[string](\"I EXIST!\")\n```\n\nAnd of course `MapErr` does the same thing but allows you to return an error:\n\n```go\nmaybe := opt.New(\"5629\")\nmaybe, err := opt.MapErr(maybe, strconv.Atoi)\n// opt.Optional[int](5629)\n\nmaybe_not := opt.New(\"not a number :(\")\nmaybe_not, err := opt.MapErr(maybe, strconv.Atoi)\n// Empty optional plus the error from Atoi.\n```\n\nAnd, as an escape hatch, a `.String()` method which is useful for tests:\n\n```go\nmaybe := opt.New(\"5629\")\nmaybe.String()\n// \"5629\"\n```\n\nIf the value exists, it'll use `fmt` to stringify, if not it'll just be empty.\n\n### When there's nothing inside\n\nThere are also methods to deal with empty values `Or`, `OrZero` and `OrCall`:\n\n```go\nmaybe := opt.New(\"I exist!\")\nmaybe.Or(\"I don't exist!\") // \"I exist!\"\n```\n\n```go\nmaybe := opt.Empty[string]()\nmaybe.Or(\"I don't exist!\") // \"I don't exist!\"\n```\n\nThe `Or` method simply lets you return a default value if the optional value is\nempty. This is handy for providing defaults.\n\nThe `OrZero` method simply returns the type's zero-value:\n\n```go\nmaybe := opt.Empty[time.Time]()\nt := maybe.OrZero()\nt.IsZero() // true\n```\n\nAnd finally, `OrCall` lets you call a function to provide a default value:\n\n```go\nmaybe := opt.Empty[string]()\nt := maybe.OrCall(func() string {\n    return \"a default value from somewhere\"\n})\n// \"a default value from somewhere\"\n```\n\n## Curried `C` Functions\n\nSome APIs will have a second version with `C` appended to the name. These are\ncurried versions of those functions to aid in ergonomic usage.\n\nSay for example you have a function that converts a number to a GBP currency\nrepresentation. You want to apply this function to a few values in a struct or\nto a slice of items.\n\n```go\n// Given: ConvertUSD(value int) string\n\nfunc Convert(input Table) PriceBreakdown {\n    return PriceBreakdown{\n        Cost:          ConvertGBP(input.UnitCost),\n        ShippingFee:   NewPtrMap(input.ShippingFee, ConvertGBP),\n        ServiceCharge: NewPtrMap(input.ServiceCharge, ConvertGBP),\n        Discount:      NewPtrMap(input.Discount, ConvertGBP),\n    }\n}\n```\n\nA small example, but you could imagine how much this can get in a larger system.\n\nUsing curried APIs, we can make this a little more terse:\n\n```go\nfunc Convert(input Table) PriceBreakdown {\n    gbp := NewPtrMapC(ConvertGBP)\n    return PriceBreakdown{\n        Cost:          ConvertGBP(input.UnitCost),\n        ShippingFee:   gbp(input.ShippingFee),\n        ServiceCharge: gbp(input.ServiceCharge),\n        Discount:      gbp(input.Discount),\n    }\n}\n```\n\nNow this may not seem like much but it can make refactors easier and keep diffs\nsmall. Once you start thinking in curried functions, certain tasks get simpler!\n\nLet's see what this looks like for a slice of items:\n\n```go\nfunc ConvertMany(prices []*int) []Optional[string] {\n    output := []Optional[string]{}\n    for _, v := range prices {\n        output = append(output, NewPtrMap(v, ConvertGBP))\n    }\n    return output\n}\n```\n\nIf you like to use functional libraries like [lo](https://github.com/samber/lo)\nand [fp-go](https://github.com/repeale/fp-go) then this might be useful:\n\n```go\nfunc ConvertMany(prices []*int) []Optional[string] {\n    fn := PtrMapC(ConvertGBP)\n    mapper := fp.Map(fn)\n    return mapper(prices)\n}\n```\n\n## Construction\n\nThere are quite a few places data can come from. opt provides a few helpers to\ncreate optional wrappers from various sources.\n\nWe've covered the boring ones already, `New` and `NewEmpty` just create values\nfrom either something or nothing.\n\n### `NewMap`\n\nThis tool creates an optional type but facilitates mapping the data type using a\nfunction first. This is similar to `.map( x =\u003e y )` in many other languages.\n\n```go\nv := opt.NewMap(\"hello\", strings.ToUpper)\n```\n\n`v` now contains an optional `string` value set to `\"HELLO\"`. because, before\nstoring the data, it passed the input value through `strings.ToUpper`.\n\n### `NewSafe`\n\nA common Go pattern is return values that look like `(T, bool)` where the bool\nrepresents validity. `NewSafe` lets you easily build optional values from this.\n\n```go\n// where getThing is: func getThing() (v string, ok bool)\nv := opt.NewSafe(getThing())\n```\n\nIt's also just handy sometimes for simple logic:\n\n```go\nv := opt.NewSafe(account.Email, account.IsEmailPublic)\n```\n\nHere, we're storing the optional value of the account's email only if the value\nof `IsEmailPublic` is true.\n\nSadly this does not work with built-in operations:\n\n```go\nhash := map[string]string{\"s\": \"asd\"}\nNewSafe(hash[\"dsf\"])\n// not enough arguments in call to NewSafe have (string) want (T, bool)\n\nvar cast any = \"hi\"\nNewSafe(cast.(string))\n// not enough arguments in call to NewSafe have (string) want (T, bool)\n```\n\nThis is because the bool part of these expressions is optional.\n\n### `NewIf`\n\nThis one is another way to encode optionality based on some branching logic. In\nthis variant, the logic exists within a closure that returns a bool.\n\n```go\nv := opt.NewIf(account.Email, isValidEmailAddress)\nv := opt.NewIf(company.LegalName, func(s string) bool { return s != \"\" })\nv := opt.NewIf(createdAt, func(t time.Time) bool { return !t.IsZero() })\n```\n\n### `NewPtr`, `NewPtrMap`, `NewPtrIf` and `NewPtrOr`\n\nIf one area of your application is using pointers already but you want to expose\noptionals, you can use this one to easily construct an optional from a pointer.\n\n```go\ntype Account struct {\n    Twitter *string\n}\n\n// ...\n\nv := opt.NewPtr(account.Twitter)\nv := opt.NewPtrOr(account.Twitter, \"@southclaws\")\n```\n\n## Prior Art\n\n- https://github.com/leighmcculloch/go-optional\n- https://github.com/samber/mo\n- https://github.com/phelmkamp/valor\n\n## Contributing\n\nIssues and pull requests welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsouthclaws%2Fopt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsouthclaws%2Fopt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsouthclaws%2Fopt/lists"}