{"id":31287751,"url":"https://github.com/rshindo/jfunc","last_synced_at":"2025-09-24T11:07:27.798Z","repository":{"id":313437756,"uuid":"1051436129","full_name":"rshindo/jfunc","owner":"rshindo","description":"Sealed sum types for Java: Option/Either/Result/Try. Minimal, pattern-matching-first APIs.","archived":false,"fork":false,"pushed_at":"2025-09-19T04:10:49.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-19T05:42:10.433Z","etag":null,"topics":["either","functional-programming","java","library","maven","option","pattern-matching","result","rop","sealed-interfaces","try"],"latest_commit_sha":null,"homepage":null,"language":"Java","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/rshindo.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,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-06T01:58:26.000Z","updated_at":"2025-09-19T04:10:52.000Z","dependencies_parsed_at":"2025-09-06T04:07:31.164Z","dependency_job_id":"af9d649d-05d5-473d-a741-758a0c387217","html_url":"https://github.com/rshindo/jfunc","commit_stats":null,"previous_names":["rshindo/jfunc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rshindo/jfunc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rshindo%2Fjfunc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rshindo%2Fjfunc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rshindo%2Fjfunc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rshindo%2Fjfunc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rshindo","download_url":"https://codeload.github.com/rshindo/jfunc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rshindo%2Fjfunc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276737524,"owners_count":25695700,"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","status":"online","status_checked_at":"2025-09-24T02:00:09.776Z","response_time":97,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["either","functional-programming","java","library","maven","option","pattern-matching","result","rop","sealed-interfaces","try"],"created_at":"2025-09-24T11:07:22.111Z","updated_at":"2025-09-24T11:07:27.786Z","avatar_url":"https://github.com/rshindo.png","language":"Java","readme":"# jfunc\n\nA tiny, typed functional utilities library for Java. Sealed, Java‑friendly sum types with minimal APIs and pattern‑matching first design.\n\n## Features\n- `Option\u003cT\u003e`: `Some`/`None` (sealed interface + records)\n  - Ops: `map`, `flatMap`, `filter`, `ifPresent`\n  - Interop: `toOptional()`, `fromOptional(Optional)`, `stream()`\n  - Constructors: `some(T)`, `none()`, `ofNullable(T)`\n  - Null policy: `some(null)` throws; `map` returning `null` becomes `None`\n- `Either\u003cL,R\u003e`: `Left`/`Right` disjoint union\n  - Right‑biased: `map`, `flatMap` operate on `Right`; `mapLeft`/`ifLeft` for `Left`\n  - Utilities: `swap()`, `toOptionRight()`, `toOptionLeft()`\n- `Result\u003cT,E\u003e`: Success/Failure for Railway Oriented Programming (ROP)\n  - Right‑biased: `map`, `flatMap` on `Success`; `mapFailure`/`onFailure` for failures\n  - Interop: `toOptionSuccess()`, `toOptionFailure()`\n  - Minimal API: prefer switch pattern matching over helpers\n- `Try\u003cT\u003e`: Success/Failure for computations that may throw\n  - Construct: `Try.of(CheckedSupplier)` to capture exceptions as `Failure`\n  - Right‑biased: `map`, `flatMap` on `Success`; side effects via `onSuccess`/`onFailure`\n  - Interop: `toOptionSuccess()`, `toOptionFailure()`, `toEither()`, `toResult()`\n\n## Requirements\n- Java 21+ (project currently compiles and runs tests on 21)\n- Maven (for building/testing)\n\n## Install\nAdd the dependency to your build. Replace the version as appropriate.\n\nMaven:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.rshindo\u003c/groupId\u003e\n  \u003cartifactId\u003ejfunc\u003c/artifactId\u003e\n  \u003cversion\u003e0.0.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nGradle (Kotlin DSL):\n\n```kts\ndependencies {\n  implementation(\"com.github.rshindo:jfunc:0.0.1-SNAPSHOT\")\n}\n```\n\n## Quick Start\n\n### Tuple\n```java\nimport com.github.rshindo.jfunc.Tuple;\n\n// Construct via factories\nTuple.Tuple2\u003cString, Integer\u003e p = Tuple.of(\"id\", 42);\nTuple t = Tuple.of(\"x\", 1, true);\n\n// Pattern matching (record patterns)\nString desc = switch (t) {\n    case Tuple.Tuple3(var a, var b, var c) -\u003e a + \":\" + b + \":\" + c;\n    default -\u003e \"other\";\n};\n\nint arity = t.arity(); // 3\n```\n\n### Option\n```java\nimport com.github.rshindo.jfunc.Option;\n\nOption\u003cInteger\u003e a = Option.some(10);\nOption\u003cInteger\u003e b = Option.none();\n\n// Map / FlatMap / Filter\nOption\u003cString\u003e ms = a.map(x -\u003e x == 10 ? \"ten\" : null); // Some(\"ten\")\nOption\u003cInteger\u003e mf = a.flatMap(x -\u003e x % 2 == 0 ? Option.some(x / 2) : Option.none());\nOption\u003cInteger\u003e fl = a.filter(x -\u003e x % 2 == 0); // Some(10)\n\n// Pattern matching (Java 21+)\nString label = switch (a) {\n    case Option.Some(var v) -\u003e \"SOME:\" + v;\n    case Option.None()      -\u003e \"NONE\";\n};\n\n// Interop\njava.util.Optional\u003cInteger\u003e opt = a.toOptional();\nOption\u003cInteger\u003e fromOpt = Option.fromOptional(opt);\n```\n\n### Either\n```java\nimport com.github.rshindo.jfunc.Either;\n\nEither\u003cString, Integer\u003e e = Math.random() \u003e 0.5 ? Either.right(42) : Either.left(\"oops\");\n\n// Right-biased ops\nEither\u003cString, String\u003e em = e.map(x -\u003e \"v=\" + x);\nEither\u003cInteger, Integer\u003e ml = e.mapLeft(String::length);\n\n// Pattern matching\nString lab = switch (e) {\n    case Either.Right(var r) -\u003e \"RIGHT:\" + r;\n    case Either.Left(var l)  -\u003e \"LEFT:\" + l;\n};\n\n// Option conversions\nOption\u003cInteger\u003e rightOpt = e.toOptionRight();\nOption\u003cString\u003e  leftOpt  = e.toOptionLeft();\n```\n\n### Result (ROP)\n```java\nimport com.github.rshindo.jfunc.Result;\nimport com.github.rshindo.jfunc.Option;\nimport java.util.function.Function;\n\n// Step functions (String -\u003e Result\u003c...\u003e)\nFunction\u003cString, Result\u003cString, String\u003e\u003e notBlank = s -\u003e\n        (s == null || s.isBlank()) ? Result.failure(\"empty\") : Result.success(s.trim());\n\nFunction\u003cString, Result\u003cInteger, String\u003e\u003e parseInt = s -\u003e {\n    try { return Result.success(Integer.parseInt(s)); }\n    catch (NumberFormatException e) { return Result.failure(\"nan:\" + s); }\n};\n\nFunction\u003cInteger, Result\u003cInteger, String\u003e\u003e evenOnly = n -\u003e\n        (n % 2 == 0) ? Result.success(n) : Result.failure(\"odd:\" + n);\n\n// Pipeline\nString input = \"42\";\nResult\u003cInteger, String\u003e res = Result.success(input)\n        .flatMap(notBlank)\n        .flatMap(parseInt)\n        .flatMap(evenOnly);\n\n// Pattern matching\nString msg = switch (res) {\n    case Result.Success(var v) -\u003e \"OK:\" + v;\n    case Result.Failure(var e) -\u003e \"ERR:\" + e;\n};\n\n// Option conversions\nOption\u003cInteger\u003e okOpt  = res.toOptionSuccess();\nOption\u003cString\u003e  errOpt = res.toOptionFailure();\n```\n\n### Try\n```java\nimport com.github.rshindo.jfunc.Try;\nimport com.github.rshindo.jfunc.Unit;\n\nTry\u003cInteger\u003e t = Try.of(() -\u003e Integer.parseInt(\"123\"));\nTry\u003cInteger\u003e u = Try.of(() -\u003e Integer.parseInt(\"not-a-number\"));\n\n// Map/flatMap\nTry\u003cString\u003e tm = t.map(x -\u003e \"v=\" + x);\n\n// Pattern matching\nString tl = switch (u) {\n    case Try.Success(var v) -\u003e \"OK:\" + v;\n    case Try.Failure(var e) -\u003e \"ERR:\" + e.getMessage();\n};\n\n// Conversions\nEither\u003cThrowable,Integer\u003e te = t.toEither();\nResult\u003cInteger,Throwable\u003e tr = u.toResult();\nOption\u003cInteger\u003e tOk = t.toOptionSuccess();\nOption\u003cThrowable\u003e tNg = u.toOptionFailure();\n\n// Void-like side effects via Unit\nTry\u003cUnit\u003e run = Try.run(() -\u003e doSideEffect());\nswitch (run) {\n    case Try.Success(var u2) -\u003e System.out.println(\"done\");\n    case Try.Failure(var e2) -\u003e System.err.println(\"failed: \" + e2.getMessage());\n}\n```\n\nHint: Try.of + try-with-resources\n```java\nimport com.github.rshindo.jfunc.Try;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.charset.StandardCharsets;\n\n// Read first line from a file\nTry\u003cString\u003e firstLine = Try.of(() -\u003e {\n    try (var br = Files.newBufferedReader(Path.of(\"data.txt\"), StandardCharsets.UTF_8)) {\n        return br.readLine(); // null -\u003e Failure via Try.success(null) rule\n    }\n});\n\nString msg = switch (firstLine) {\n    case Try.Success(var v) -\u003e \"OK: \" + v;\n    case Try.Failure(var e) -\u003e \"ERR: \" + e.getMessage();\n};\n\n// Count lines with multiple resources\nTry\u003cLong\u003e count = Try.of(() -\u003e {\n    try (var in = Files.newInputStream(Path.of(\"data.txt\"));\n         var br = new java.io.BufferedReader(new java.io.InputStreamReader(in, StandardCharsets.UTF_8))) {\n        return br.lines().count();\n    }\n});\n\ncount.onSuccess(c -\u003e System.out.println(\"lines: \" + c))\n     .onFailure(e -\u003e System.err.println(\"read failed: \" + e.getMessage()));\n```\n\n## Semantics \u0026 Design\n- Sealed + nested records: `Option`, `Either`, and `Result` are sealed interfaces with nested record variants.\n- Pattern‑matching first: prefer Java `switch`/type patterns; helper methods like `fold` are deliberately not included.\n- Null policy:\n  - `Option.some(null)` throws; use `ofNullable` to map `null` to `None`.\n  - `Either` and `Result` disallow `null` in both variants; mappers must not return `null`.\n  - `Try` disallows `null` success values; mappers must not return `null`. Failures carry a non-null `Throwable`.\n- Bias:\n  - `Either` and `Result` are right‑biased: `map`/`flatMap` act on `Right`/`Success`.\n  - Use `mapLeft` (Either) or `mapFailure` (Result) for the left/failure path.\n  - `Try` is right‑biased: `map`/`flatMap` act on `Success`; use `onFailure` for side-effects.\n- Equality: record value equality; distinct `None` instances compare equal.\n- No `None` singleton: `Option.none()` returns a new instance by design.\n\n## Development\n- Run tests: `mvn test`\n- Test framework: JUnit Jupiter 5 via Maven Surefire\n- See also: `AGENTS.md` for working guidelines and API design policies\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frshindo%2Fjfunc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frshindo%2Fjfunc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frshindo%2Fjfunc/lists"}