{"id":15608881,"url":"https://github.com/serradura/kind","last_synced_at":"2026-03-05T02:03:14.710Z","repository":{"id":46818483,"uuid":"229931976","full_name":"serradura/kind","owner":"serradura","description":"A development toolkit for Ruby with several small/cohesive abstractions to empower your development workflow - It's totally free of dependencies.","archived":false,"fork":false,"pushed_at":"2022-10-27T03:06:40.000Z","size":435,"stargazers_count":40,"open_issues_count":3,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-23T00:40:55.064Z","etag":null,"topics":["activemodel-validations","maybe-monad","ruby","rubygem","type-checking","type-system"],"latest_commit_sha":null,"homepage":"http://rubygems.org/gems/kind","language":"Ruby","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/serradura.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-12-24T11:45:59.000Z","updated_at":"2025-02-04T00:16:16.000Z","dependencies_parsed_at":"2022-08-20T23:10:45.587Z","dependency_job_id":null,"html_url":"https://github.com/serradura/kind","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/serradura/kind","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fkind","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fkind/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fkind/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fkind/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serradura","download_url":"https://codeload.github.com/serradura/kind/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serradura%2Fkind/sbom","scorecard":{"id":812793,"data":{"date":"2025-08-11","repo":{"name":"github.com/serradura/kind","commit":"9a97cc1e18278295aa90a7eecbcf7f7bef4d49b4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":4,"reason":"Found 4/10 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/serradura/kind/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/serradura/kind/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/serradura/kind/ci.yml/main?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T13:33:59.195Z","repository_id":46818483,"created_at":"2025-08-23T13:33:59.195Z","updated_at":"2025-08-23T13:33:59.195Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30106155,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T01:39:18.192Z","status":"online","status_checked_at":"2026-03-05T02:00:06.710Z","response_time":93,"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":["activemodel-validations","maybe-monad","ruby","rubygem","type-checking","type-system"],"created_at":"2024-10-03T05:40:23.806Z","updated_at":"2026-03-05T02:03:14.689Z","avatar_url":"https://github.com/serradura.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e🤷 kind\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003ci\u003eA development toolkit for Ruby with several small/cohesive abstractions to empower your development workflow - It's totally free of dependencies.\u003c/i\u003e\u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://rubygems.org/gems/kind\"\u003e\n    \u003cimg alt=\"Gem\" src=\"https://img.shields.io/gem/v/kind.svg?style=flat-square\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://github.com/serradura/kind/actions/workflows/ci.yml\"\u003e\n    \u003cimg alt=\"Build Status\" src=\"https://github.com/serradura/kind/actions/workflows/ci.yml/badge.svg\"\u003e\n  \u003c/a\u003e\n\n  \u003cbr /\u003e\n\n  \u003cimg src=\"https://img.shields.io/badge/ruby%20%3E=%202.1,%20%3C%203.1-ruby.svg?colorA=99004d\u0026colorB=cc0066\" alt=\"Ruby\"\u003e\n\n  \u003cimg src=\"https://img.shields.io/badge/rails%20%3E=%203.2.0,%20%3C%207.0-rails.svg?colorA=8B0000\u0026colorB=FF0000\" alt=\"Rails\"\u003e\n\n  \u003cbr /\u003e\n\n  \u003ca href=\"https://codeclimate.com/github/serradura/kind/maintainability\"\u003e\n    \u003cimg alt=\"Maintainability\" src=\"https://api.codeclimate.com/v1/badges/711329decb2806ccac95/maintainability\"\u003e\n  \u003c/a\u003e\n\n  \u003ca href=\"https://codeclimate.com/github/serradura/kind/test_coverage\"\u003e\n    \u003cimg alt=\"Test Coverage\" src=\"https://api.codeclimate.com/v1/badges/711329decb2806ccac95/test_coverage\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n**Motivation:**\n\nThis project was born to help me with a simple task, create a light and fast type checker (at runtime) for Ruby. The initial idea was to have something to raise an exception when a method or function (procs) received a wrong input.\n\nBut through time it was natural the addition of more features to improve the development workflow, like monads ([`Kind::Maybe`](#kindmaybe), `Kind::Either` / `Kind::Result`),  enums (`Kind::Enum`), immutable objects (`Kind::ImmutableAttributes`), [type validation via ActiveModel::Validation](#kindvalidator-activemodelvalidations), and several abstractions to help the implementation of business logic (`Kind::Functional::Steps`, `Kind::Functional::Action`, `Kind::Action`).\n\nSo, I invite you to check out these features to see how they could be useful for you. Enjoy!\n\n## Documentation \u003c!-- omit in toc --\u003e\n\nVersion    | Documentation\n---------- | -------------\nunreleased | https://github.com/serradura/kind/blob/main/README.md\n5.10.0     | https://github.com/serradura/kind/blob/v5.x/README.md\n4.1.0      | https://github.com/serradura/kind/blob/v4.x/README.md\n3.1.0      | https://github.com/serradura/kind/blob/v3.x/README.md\n2.3.0      | https://github.com/serradura/kind/blob/v2.x/README.md\n1.9.0      | https://github.com/serradura/kind/blob/v1.x/README.md\n\n## Table of Contents \u003c!-- omit in toc --\u003e\n- [Compatibility](#compatibility)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Kind.\\\u003cType\\\u003e[]](#kindtype)\n  - [Kind::\\\u003cType\\\u003e.===()](#kindtype-1)\n  - [Kind::\\\u003cType\\\u003e.value?()](#kindtypevalue)\n  - [Kind::\\\u003cType\\\u003e.or_nil()](#kindtypeor_nil)\n  - [Kind::\\\u003cType\\\u003e.or_undefined()](#kindtypeor_undefined)\n  - [Kind::\\\u003cType\\\u003e.or()](#kindtypeor)\n  - [Kind::\\\u003cType\\\u003e.value()](#kindtypevalue-1)\n  - [Kind::\\\u003cType\\\u003e.maybe](#kindtypemaybe)\n  - [Kind::\\\u003cType\\\u003e?](#kindtype-2)\n  - [Kind::{Array,Hash,String,Set}.empty_or()](#kindarrayhashstringsetempty_or)\n  - [List of all type handlers](#list-of-all-type-handlers)\n    - [Core](#core)\n    - [Stdlib](#stdlib)\n    - [Custom](#custom)\n  - [Creating type handlers](#creating-type-handlers)\n    - [Dynamic creation](#dynamic-creation)\n      - [Using a class or a module](#using-a-class-or-a-module)\n      - [Using Kind.object(name:, \u0026block)](#using-kindobjectname-block)\n    - [Kind::\u003cType\u003e object](#kindtype-object)\n  - [Utility methods](#utility-methods)\n    - [Kind.of_class?()](#kindof_class)\n    - [Kind.of_module?()](#kindof_module)\n    - [Kind.of_module_or_class()](#kindof_module_or_class)\n    - [Kind.respond_to()](#kindrespond_to)\n    - [Kind.of()](#kindof)\n    - [Kind.of?()](#kindof-1)\n    - [Kind.value()](#kindvalue)\n    - [Kind.is()](#kindis)\n  - [Utility modules](#utility-modules)\n    - [Kind::Try](#kindtry)\n    - [Kind::Dig](#kinddig)\n    - [Kind::Presence](#kindpresence)\n- [Kind::Undefined](#kindundefined)\n- [Kind::Maybe](#kindmaybe)\n    - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas)\n  - [Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases](#kindmaybe-kindmaybewrap-and-kindmaybethen-method-aliases)\n    - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-1)\n  - [Kind::None() and Kind::Some()](#kindnone-and-kindsome)\n  - [Kind::Optional](#kindoptional)\n    - [Replacing blocks by lambdas](#replacing-blocks-by-lambdas-2)\n  - [Kind::Maybe(\u003cType\u003e)](#kindtypemaybe)\n    - [Real world examples](#real-world-examples)\n  - [Error handling](#error-handling)\n    - [Kind::Maybe.wrap {}](#kindmaybewrap-)\n    - [Kind::Maybe.map! or Kind::Maybe.then!](#kindmaybemap-or-kindmaybethen)\n  - [Kind::Maybe#try](#kindmaybetry)\n  - [Kind::Maybe#try!](#kindmaybetry-1)\n  - [Kind::Maybe#dig](#kindmaybedig)\n  - [Kind::Maybe#check](#kindmaybecheck)\n  - [Kind::Maybe#presence](#kindmaybepresence)\n- [Kind::Empty](#kindempty)\n  - [Defining Empty as Kind::Empty an alias](#defining-empty-as-kindempty-an-alias)\n- [Kind::Validator (ActiveModel::Validations)](#kindvalidator-activemodelvalidations)\n  - [Usage](#usage-1)\n    - [Object#===](#object)\n    - [Kind.is](#kindis-1)\n    - [Object#instance_of?](#objectinstance_of)\n    - [Object#respond_to?](#objectrespond_to)\n    - [Array.new.all? { |item| item.kind_of?(Class) }](#arraynewall--item-itemkind_ofclass-)\n    - [Array.new.all? { |item| expected_values.include?(item) }](#arraynewall--item-expected_valuesincludeitem-)\n  - [Defining the default validation strategy](#defining-the-default-validation-strategy)\n  - [Using the `allow_nil` and `strict` options](#using-the-allow_nil-and-strict-options)\n- [Similar Projects](#similar-projects)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n- [Code of Conduct](#code-of-conduct)\n\n## Compatibility\n\n| kind           | branch  | ruby               | activemodel    |\n| -------------- | ------- | ------------------ | -------------- |\n| unreleased     | main    | \u003e= 2.1.0, \u003c= 3.0.0 | \u003e= 3.2, \u003c 7.0  |\n| 5.10.0         | v5.x    | \u003e= 2.1.0, \u003c= 3.0.0 | \u003e= 3.2, \u003c 7.0  |\n| 4.1.0          | v4.x    | \u003e= 2.2.0, \u003c= 3.0.0 | \u003e= 3.2, \u003c 7.0  |\n| 3.1.0          | v3.x    | \u003e= 2.2.0, \u003c= 2.7   | \u003e= 3.2, \u003c 7.0  |\n| 2.3.0          | v2.x    | \u003e= 2.2.0, \u003c= 2.7   | \u003e= 3.2, \u003c= 6.0 |\n| 1.9.0          | v1.x    | \u003e= 2.2.0, \u003c= 2.7   | \u003e= 3.2, \u003c= 6.0 |\n\n\u003e Note: The activemodel is an optional dependency, it is related with the [Kind::Validator](#kindvalidator-activemodelvalidations).\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'kind'\n```\n\nAnd then execute:\n\n    $ bundle install\n\nOr install it yourself as:\n\n    $ gem install kind\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Usage\n\nWith this gem you can add some kind of type checking at runtime. e.g:\n\n```ruby\ndef sum(a, b)\n  Kind::Numeric[a] + Kind::Numeric[b]\nend\n\nsum(1, 1)   # 2\n\nsum('1', 1) # Kind::Error (\"\\\"1\\\" expected to be a kind of Numeric\")\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind.\\\u003cType\\\u003e[]\n\nBy default, basic verifications are strict. So, when you perform `Kind::Hash[value]` the given value will be returned if it was a Hash, but if not, an error will be raised.\n\n```ruby\nKind::Hash[nil]  # Kind::Error (nil expected to be a kind of Hash)\nKind::Hash['']   # Kind::Error (\"\" expected to be a kind of Hash)\nKind::Hash[a: 1] # {a: 1}\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.===()\n\nUse this method to verify if the given object has the expected type.\n\n```ruby\nKind::Enumerable === {} # true\nKind::Enumerable === '' # false\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.value?()\n\nThis method works like `.===`, but the difference is what happens when you invoke it without arguments.\n\n```ruby\n# Example of calling `.value?` with an argument:\n\nKind::Enumerable.value?({}) # true\nKind::Enumerable.value?('') # false\n```\n\nWhen `.value?` is called without an argument, it will return a lambda which will know how to perform the kind verification.\n\n```ruby\ncollection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]\n\ncollection.select(\u0026Kind::Enumerable.value?) # [{:number=\u003e1}, {:number=\u003e3}, [:number, 5]]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.or_nil()\n\nBut if you don't need a strict type verification, use the `.or_nil` method.\n\n```ruby\nKind::Hash.or_nil('')     # nil\nKind::Hash.or_nil({a: 1}) # {a: 1}\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.or_undefined()\n\nThis method works like `.or_nil`, but it will return a [`Kind::Undefined`](#kindundefined) instead of `nil`.\n\n```ruby\nKind::Hash.or_undefined('')     # Kind::Undefined\nKind::Hash.or_undefined({a: 1}) # {a: 1}\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.or()\n\nThis method can return a fallback if the given value isn't an instance of the expected kind.\n\n```ruby\nKind::Hash.or({}, [])      # {}\nKind::Hash.or(nil, [])     # nil\nKind::Hash.or(nil, {a: 1}) # {a: 1}\n```\n\nIf it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value is wrong.\n\n```ruby\ncollection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]\n\ncollection.map(\u0026Kind::Hash.or({}))  # [{:number=\u003e1}, {}, {:number=\u003e3}, {}, {}]\ncollection.map(\u0026Kind::Hash.or(nil)) # [{:number=\u003e1}, nil, {:number=\u003e3}, nil, nil]\n```\n\nAn error will be raised if the fallback didn't have the expected kind or if not `nil` / `Kind::Undefined`.\n\n```ruby\ncollection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]\n\ncollection.map(\u0026Kind::Hash.or(:foo)) # Kind::Error (:foo expected to be a kind of Hash)\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.value()\n\nThis method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.\n```ruby\nKind::String.value(1, default: '')   # \"\"\n\nKind::String.value('1', default: '') # \"1\"\n\nKind::String.value('1', default: 1)  # Kind::Error (1 expected to be a kind of String)\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e.maybe\n\nThis method exposes a [typed `Kind::Maybe`](#kindtypemaybe) and using it will be possible to apply a sequence of operations in the case of the wrapped value has the expected kind.\n\n```ruby\nDouble = -\u003e(value) do\n  Kind::Numeric.maybe(value)\n               .then { |number| number * 2 }\n               .value_or(0)\nend\n\nDouble.('2') # 0\nDouble.(2)   # 4\n```\n\nIf it is invoked without arguments, it returns the typed Maybe. But, if it receives arguments, it will behave like the `Kind::Maybe.wrap` method. e.g.\n\n```ruby\nKind::Integer.maybe #\u003cKind::Maybe::Typed:0x0000... @kind=Kind::Integer\u003e\n\nKind::Integer.maybe(0).some?               # true\nKind::Integer.maybe { 1 }.some?            # true\nKind::Integer.maybe(2) { |n| n * 2 }.some? # true\n\nKind::Integer.maybe { 2 / 0 }.none?          # true\nKind::Integer.maybe(2) { |n| n / 0 }.none?   # true\nKind::Integer.maybe('2') { |n| n * n }.none? # true\n```\n\n\u003e **Note:** You can use `Kind::\\\u003cType\\\u003e.optional` as an alias for `Kind::\\\u003cType\\\u003e.maybe`.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::\\\u003cType\\\u003e?\n\nThere is a second way to do a type verification and know if one or multiple values has the expected type. You can use the predicate kind methods (`Kind::Hash?`). e.g:\n\n```ruby\n# Verifying one value\nKind::Enumerable?({}) # true\n\n# Verifying multiple values\nKind::Enumerable?({}, [], Set.new) # true\n```\n\nLike the `Kind::\u003cType\u003e.value?` method, if the `Kind::\u003cType\u003e?` doesn't receive an argument, it will return a lambda which will know how to perform the kind verification.\n\n```ruby\ncollection = [ {number: 1}, 'number 2', {number: 3}, :number_4, [:number, 5] ]\n\ncollection.select(\u0026Kind::Enumerable?) # [{:number=\u003e1}, {:number=\u003e3}, [:number, 5]]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::{Array,Hash,String,Set}.empty_or()\n\nThis method is available for some type handlers (`Kind::Array`, `Kind::Hash`, `Kind::String`, `Kind::Set`), and it will return an empty frozen value if the given one hasn't the expected kind.\n```ruby\nKind::Array.empty_or({})         # []\nKind::Array.empty_or({}).frozen? # true\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### List of all type handlers\n\n#### Core\n\n* `Kind::Array`\n* `Kind::Class`\n* `Kind::Comparable`\n* `Kind::Enumerable`\n* `Kind::Enumerator`\n* `Kind::File`\n* `Kind::Float`\n* `Kind::Hash`\n* `Kind::Integer`\n* `Kind::IO`\n* `Kind::Method`\n* `Kind::Module`\n* `Kind::Numeric`\n* `Kind::Proc`\n* `Kind::Queue`\n* `Kind::Range`\n* `Kind::Regexp`\n* `Kind::String`\n* `Kind::Struct`\n* `Kind::Symbol`\n* `Kind::Time`\n\n#### Stdlib\n\n* `Kind::OpenStruct`\n* `Kind::Set`\n\n#### Custom\n\n* `Kind::Boolean`\n* `Kind::Callable`\n* `Kind::Lambda`\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Creating type handlers\n\nThere are two ways to do this, you can create type handlers dynamically or defining a module.\n\n#### Dynamic creation\n\n##### Using a class or a module\n\n```ruby\nclass User\nend\n\nuser = User.new\n\nkind_of_user = Kind[User]\n\n# kind_of_user.name\n# kind_of_user.kind\n# The type handler can return its kind and its name\nkind_of_user.name # \"User\"\nkind_of_user.kind # User\n\n# kind_of_user.===\n# Can check if a given value is an instance of its kind.\nkind_of_user === 0        # false\nkind_of_user === User.new # true\n\n# kind_of_user.value?(value)\n# Can check if a given value is an instance of its kind.\nkind_of_user.value?('')       # false\nkind_of_user.value?(User.new) # true\n\n# If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.\n[0, User.new].select(\u0026kind_of_user.value?) # [#\u003cUser:0x0000.... \u003e]\n\n# kind_of_user.or_nil(value)\n# Can return nil if the given value isn't an instance of its kind\nkind_of_user.or_nil({})       # nil\nkind_of_user.or_nil(User.new) # #\u003cUser:0x0000.... \u003e\n\n# kind_of_user.or_undefined(value)\n# Can return Kind::Undefined if the given value isn't an instance of its kind\nkind_of_user.or_undefined([])       # Kind::Undefined\nkind_of_user.or_undefined(User.new) # #\u003cUser:0x0000.... \u003e\n\n# kind_of_user.or(fallback, value)\n# Can return a fallback if the given value isn't an instance of its kind\nkind_of_user.or(nil, 0)        # nil\nkind_of_user.or(nil, User.new) # #\u003cUser:0x0000.... \u003e\n\n# If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.\n[1, User.new].map(\u0026kind_of_user.or(nil)) # [nil, #\u003cUser:0x0000.... \u003e]\n\n# An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.\n[0, User.new].map(\u0026kind_of_user.or(:foo)) # Kind::Error (:foo expected to be a kind of User)\n\n# kind_of_user[value]\n# Will raise Kind::Error if the given value isn't an instance of the expected kind\nkind_of_user[:foo]     # Kind::Error (:foo expected to be a kind of User)\nkind_of_user[User.new] # #\u003cUser:0x0000.... \u003e\n\n# kind_of_user.value(arg, default:)\n# This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.\nkind_of_user.value(User.new, default: User.new) # #\u003cUser:0x0000...\u003e\n\nkind_of_user.value('1', default: User.new)      # #\u003cUser:0x0000...\u003e\n\nkind_of_user.value('1', default: 1)  # Kind::Error (1 expected to be a kind of User)\n\n# kind_of_user.maybe\n# This method returns a typed Kind::Maybe.\nkind_of_user.maybe('1').value_or(User.new) # #\u003cUser:0x0000...\u003e\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n##### Using Kind.object(name:, \u0026block)\n\n```ruby\nPositiveInteger = Kind.object(name: 'PositiveInteger') do |value|\n  value.kind_of?(Integer) \u0026\u0026 value \u003e 0\nend\n\n# PositiveInteger.name\n# PositiveInteger.kind\n# The type handler can return its kind and its name\nPositiveInteger.name # \"PositiveInteger\"\nPositiveInteger.kind # #\u003cProc:0x0000.... \u003e\n\n# PositiveInteger.===\n# Can check if a given value is an instance of its kind.\nPositiveInteger === 1 # true\nPositiveInteger === 0 # false\n\n# PositiveInteger.value?(value)\n# Can check if a given value is an instance of its kind.\nPositiveInteger.value?(1)  # true\nPositiveInteger.value?(-1) # false\n\n# If it doesn't receive an argument, a lambda will be returned and it will know how to do the type verification.\n[1, 2, 0, 3, -1].select(\u0026PositiveInteger.value?) # [1, 2, 3]\n\n# PositiveInteger.or_nil(value)\n# Can return nil if the given value isn't an instance of its kind\nPositiveInteger.or_nil(1) # 1\nPositiveInteger.or_nil(0) # nil\n\n# PositiveInteger.or_undefined(value)\n# Can return Kind::Undefined if the given value isn't an instance of its kind\nPositiveInteger.or_undefined(2)  # 2\nPositiveInteger.or_undefined(-1) # Kind::Undefined\n\n# PositiveInteger.or(fallback, value)\n# Can return a fallback if the given value isn't an instance of its kind\nPositiveInteger.or(nil, 1) # 1\nPositiveInteger.or(nil, 0) # nil\n\n# If it doesn't receive a second argument (the value), it will return a callable that knows how to expose an instance of the expected type or a fallback if the given value was wrong.\n[1, 2, 0, 3, -1].map(\u0026PositiveInteger.or(1))   # [1, 2, 1, 3, 1]\n[1, 2, 0, 3, -1].map(\u0026PositiveInteger.or(nil)) # [1, 2, nil, 3, nil]\n\n# An error will be raised if the fallback didn't have the expected kind or if not nil / Kind::Undefined.\n[1, 2, 0, 3, -1].map(\u0026PositiveInteger.or(:foo)) # Kind::Error (:foo expected to be a kind of PositiveInteger)\n\n# PositiveInteger[value]\n# Will raise Kind::Error if the given value isn't an instance of the expected kind\nPositiveInteger[1]    # 1\nPositiveInteger[:foo] # Kind::Error (:foo expected to be a kind of PositiveInteger)\n\n# PositiveInteger.value(arg, default:)\n# This method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.\nPositiveInteger.value(2, default: 1)   # 2\n\nPositiveInteger.value('1', default: 1) # 1\n\nPositiveInteger.value('1', default: 0) # Kind::Error (0 expected to be a kind of PositiveInteger)\n\n# PositiveInteger.maybe\n# This method returns a typed Kind::Maybe.\nPositiveInteger.maybe(0).value_or(1) # 1\n\nPositiveInteger.maybe(2).value_or(1) # 2\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind::\u003cType\u003e object\n\nThe idea here is to create a type handler inside of the `Kind` namespace and to do this you will only need to use pure Ruby. e.g:\n\n```ruby\nclass User\nend\n\nmodule Kind\n  module User\n    extend self, ::Kind::Object\n\n    # Define the expected kind of this type handler.\n    def kind; ::User; end\n  end\n\n  # This how the Kind::\u003cType\u003e? methods are defined.\n  def self.User?(*values)\n    KIND.of?(::User, values)\n  end\nend\n\n# Doing this you will have the same methods of a standard type handler (like: `Kind::Symbol`).\n\nuser = User.new\n\nKind::User[user] # #\u003cUser:0x0000...\u003e\nKind::User[{}]   # Kind::Error ({} expected to be a kind of User)\n\nKind::User?(user) # true\nKind::User?({})   # false\n```\n\nThe advantages of this approach are:\n\n1. You will have a singleton (a unique instance) to be used, so the garbage collector will work less.\n2. You can define additional methods to be used with this kind.\n\nThe disadvantage is:\n\n1. You could overwrite some standard type handler or constant. I believe that this will be hard to happen, but must be your concern if you decide to use this kind of approach.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Utility methods\n\n#### Kind.of_class?()\n\nThis method verify if a given value is a `Class`.\n\n```ruby\nKind.of_class?(Hash)       # true\nKind.of_class?(Enumerable) # false\nKind.of_class?(1)          # false\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.of_module?()\n\nThis method verify if a given value is a `Module`.\n\n```ruby\nKind.of_module?(Hash)       # false\nKind.of_module?(Enumerable) # true\nKind.of_module?(1)          # false\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.of_module_or_class()\n\nThis method return the given value if it is a module or a class. If not, a `Kind::Error` will be raised.\n\n```ruby\nKind.of_module_or_class(String) # String\nKind.of_module_or_class(1)      # Kind::Error (1 expected to be a kind of Module/Class)\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.respond_to()\n\nthis method returns the given object if it responds to all of the method names. But if the object does not respond to some of the expected methods, an error will be raised.\n  ```ruby\n  Kind.respond_to('', :upcase)         # \"\"\n  Kind.respond_to('', :upcase, :strip) # \"\"\n\n  Kind.respond_to(1, :upcase)        # expected 1 to respond to :upcase\n  Kind.respond_to(2, :to_s, :upcase) # expected 2 to respond to :upcase\n  ```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.of()\n\nThere is a second way to do a strict type verification, you can use the `Kind.of()` method to do this. It receives the kind as the first argument and the value to be checked as the second one.\n```ruby\nKind.of(Hash, {}) # {}\nKind.of(Hash, []) # Kind::Error ([] expected to be a kind of Hash)\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.of?()\n\nThis method can be used to check if one or multiple values have the expected kind.\n\n```ruby\n# Checking one value\nKind.of?(Array, []) # true\nKind.of?(Array, {}) # false\n\n# Checking multiple values\nKind.of?(Enumerable, [], {}) # true\nKind.of?(Hash, {}, {})       # true\nKind.of?(Array, [], {})      # false\n```\n\nIf the method receives only the first argument (the kind) a lambda will be returned and it will know how to do the type verification.\n\n```ruby\n[1, '2', 3].select(\u0026Kind.of?(Numeric)) # [1, 3]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.value()\n\nThis method ensures that you will have a value of the expected kind. But, in the case of the given value be invalid, this method will require a default value (with the expected kind) to be returned.\n```ruby\nKind.value(String, '1', default: '') # \"1\"\n\nKind.value(String, 1, default: '')   # \"\"\n\nKind.value(String, 1, default: 2)    # Kind::Error (2 expected to be a kind of String)\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind.is()\n\nYou can use `Kind.is` to verify if some class has the expected type as its ancestor.\n\n```ruby\nKind.is(Hash, String) # false\n\nKind.is(Hash, Hash)   # true\n\nKind.is(Enumerable, Hash) # true\n```\n\nThe `Kind.is` also could check the inheritance of Classes/Modules.\n\n```ruby\n#\n# Verifying if a class is or inherits from the expected class.\n#\nclass Human; end\nclass Person \u003c Human; end\nclass User \u003c Human; end\n\nKind.is(Human, User)   # true\nKind.is(Human, Human)  # true\nKind.is(Human, Person) # true\n\nKind.is(Human, Struct) # false\n\n#\n# Verifying if the classes included a module.\n#\nmodule Human; end\nclass Person; include Human; end\nclass User; include Human; end\n\nKind.is(Human, User)   # true\nKind.is(Human, Human)  # true\nKind.is(Human, Person) # true\n\nKind.is(Human, Struct) # false\n\n#\n# Verifying if a class is or inherits from the expected module.\n#\nmodule Human; end\nmodule Person; extend Human; end\nmodule User; extend Human; end\n\nKind.is(Human, User)   # true\nKind.is(Human, Human)  # true\nKind.is(Human, Person) # true\n\nKind.is(Human, Struct) # false\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Utility modules\n\n#### Kind::Try\n\nThe method `.call` of this module invokes a public method with or without arguments like `public_send` does, except that if the receiver does not respond to it the call returns `nil` rather than raising an exception.\n\n```ruby\nKind::Try.(' foo ', :strip)        # \"foo\"\nKind::Try.({a: 1}, :[], :a)        # 1\nKind::Try.({a: 1}, :[], :b)        # nil\nKind::Try.({a: 1}, :fetch, :b, 2)  # 2\n\nKind::Try.(:symbol, :strip)        # nil\nKind::Try.(:symbol, :fetch, :b, 2) # nil\n\n# It raises an exception if the method name isn't a string or a symbol\nKind::Try.({a: 1}, 1, :a) # TypeError (1 is not a symbol nor a string)\n```\n\nThis module has the method `[]` that knows how to create a lambda that will know how to perform the `try` strategy.\n\n```ruby\nresults =\n  [\n    {},\n    {name: 'Foo Bar'},\n    {name: 'Rodrigo Serradura'},\n  ].map(\u0026Kind::Try[:fetch, :name, 'John Doe'])\n\np results # [\"John Doe\", \"Foo Bar\", \"Rodrigo Serradura\"]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind::Dig\n\nThe method `.call` of this module has the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some step can't be digged.\n\n```ruby\ns = Struct.new(:a, :b).new(101, 102)\no = OpenStruct.new(c: 103, d: 104)\nd = { struct: s, ostruct: o, data: [s, o]}\n\nKind::Dig.(s, [:a])            # 101\nKind::Dig.(o, [:c])            # 103\n\nKind::Dig.(d, [:struct, :b])   # 102\nKind::Dig.(d, [:data, 0, :b])  # 102\nKind::Dig.(d, [:data, 0, 'b']) # 102\n\nKind::Dig.(d, [:ostruct, :d])  # 104\nKind::Dig.(d, [:data, 1, :d])  # 104\nKind::Dig.(d, [:data, 1, 'd']) # 104\n\nKind::Dig.(d, [:struct, :f])   # nil\nKind::Dig.(d, [:ostruct, :f])  # nil\nKind::Dig.(d, [:data, 0, :f])  # nil\nKind::Dig.(d, [:data, 1, :f])  # nil\n```\n\nAnother difference between the `Kind::Dig` and the native Ruby dig, is that it knows how to extract values from regular objects.\n\n```ruby\nclass Person\n  attr_reader :name\n\n  def initialize(name)\n    @name = name\n  end\nend\n\nperson = Person.new('Rodrigo')\n\nKind::Dig.(person, [:name])                         # \"Rodrigo\"\n\nKind::Dig.({people: [person]}, [:people, 0, :name]) # \"Rodrigo\"\n```\n\nThis module has the method `[]` that knows how to create a lambda that will know how to perform the `dig` strategy.\n\n```ruby\nresults = [\n  { person: {} },\n  { person: { name: 'Foo Bar'} },\n  { person: { name: 'Rodrigo Serradura'} },\n].map(\u0026Kind::Dig[:person, :name])\n\np results # [nil, \"Foo Bar\", \"Rodrigo Serradura\"],\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Kind::Presence\n\nThe method `.call` of this module returns the given value if it's present otherwise it will return `nil`.\n\n```ruby\nKind::Presence.(true)         # true\nKind::Presence.('foo')        # \"foo\"\nKind::Presence.([1, 2])       # [1, 2]\nKind::Presence.({a: 3})       # {a: 3}\nKind::Presence.(Set.new([4])) # #\u003cSet: {4}\u003e\n\nKind::Presence.('')       # nil\nKind::Presence.('   ')    # nil\nKind::Presence.(\"\\t\\n\\r\") # nil\nKind::Presence.(\"\\u00a0\") # nil\n\nKind::Presence.([])       # nil\nKind::Presence.({})       # nil\nKind::Presence.(Set.new)  # nil\n\nKind::Presence.(nil)      # nil\nKind::Presence.(false)    # nil\n\n# nil will be returned if the given object responds to the method blank? and this method result is true.\nMyObject = Struct.new(:is_blank) do\n  def blank?\n    is_blank\n  end\nend\n\nmy_object = MyObject.new\n\nmy_object.is_blank = true\n\nKind::Presence.(my_object) # nil\n\nmy_object.is_blank = false\n\nKind::Presence.(my_object) # #\u003cstruct MyObject is_blank=false\u003e\n```\n\nThis module also has the method `to_proc`, because of this you can make use of the `Kind::Presence` in methods that receive a block as an argument. e.g:\n\n```ruby\n  ['', [], {}, '1', [2]].map(\u0026Kind::Presence) # [nil, nil, nil, \"1\", [2]]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Kind::Undefined\n\nThe [`Kind::Undefined`](https://github.com/serradura/kind/blob/main/lib/kind/basic/undefined.rb) constant can be used to distinguish the usage of `nil`.\n\nIf you are interested, check out [the tests](https://github.com/serradura/kind/blob/main/test/kind/basic/undefined_test.rb) to understand its methods.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Kind::Maybe\n\nThe `Kind::Maybe` is used when a series of computations (in a chain of map callings) could return `nil` or `Kind::Undefined` at any point.\n\n```ruby\noptional =\n  Kind::Maybe.new(2)\n             .map { |value| value * 2 }\n             .map { |value| value * 2 }\n\nputs optional.value # 8\nputs optional.some? # true\nputs optional.none? # false\nputs optional.value_or(0) # 8\nputs optional.value_or { 0 } # 8\n\n#################\n# Returning nil #\n#################\n\noptional =\n  Kind::Maybe.new(3)\n             .map { nil }\n             .map { |value| value * 3 }\n\nputs optional.value # nil\nputs optional.some? # false\nputs optional.none? # true\nputs optional.value_or(0) # 0\nputs optional.value_or { 0 } # 0\n\n#############################\n# Returning Kind::Undefined #\n#############################\n\noptional =\n  Kind::Maybe.new(4)\n             .map { Kind::Undefined }\n             .map { |value| value * 4 }\n\nputs optional.value # Kind::Undefined\nputs optional.some? # false\nputs optional.none? # true\nputs optional.value_or(1) # 1\nputs optional.value_or { 1 } # 1\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Replacing blocks by lambdas\n\n```ruby\nAdd = -\u003e params do\n  a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)\n\n  a + b if Kind::Numeric?(a, b)\nend\n\n# --\n\n  Kind::Maybe.new(a: 1, b: 2).map(\u0026Add).value_or(0) # 3\n\n  # --\n\n  Kind::Maybe.new([]).map(\u0026Add).value_or(0) # 0\n  Kind::Maybe.new({}).map(\u0026Add).value_or(0) # 0\n  Kind::Maybe.new(nil).map(\u0026Add).value_or(0) # 0\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe[], Kind::Maybe.wrap() and Kind::Maybe#then method aliases\n\nYou can use `Kind::Maybe[]` (brackets) instead of the `.new` to transform values in a `Kind::Maybe`. Another alias is `.then` to the `.map` method.\n\n```ruby\nresult =\n  Kind::Maybe[5]\n    .then { |value| value * 5 }\n    .then { |value| value + 17 }\n    .value_or(0)\n\nputs result # 42\n```\n\nYou can also use `Kind::Maybe.wrap()` instead of the `.new` method.\n\n```ruby\nresult =\n  Kind::Maybe\n    .wrap(5)\n    .then { |value| value * 5 }\n    .then { |value| value + 17 }\n    .value_or(0)\n\nputs result # 42\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Replacing blocks by lambdas\n\n```ruby\nAdd = -\u003e params do\n  a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)\n\n  a + b if Kind::Numeric?(a, b)\nend\n\n# --\n\nKind::Maybe[a: 1, b: 2].then(\u0026Add).value_or(0) # 3\n\n# --\n\nKind::Maybe[1].then(\u0026Add).value_or(0) # 0\nKind::Maybe['2'].then(\u0026Add).value_or(0) # 0\nKind::Maybe[nil].then(\u0026Add).value_or(0) # 0\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::None() and Kind::Some()\n\nIf you need to ensure the return of  `Kind::Maybe` results from your methods/lambdas,\nyou could use the methods `Kind::None` and `Kind::Some` to do this. e.g:\n\n```ruby\nDouble = -\u003e(arg) do\n  number = Kind::Numeric.or_nil(arg)\n\n  Kind::Maybe[number].then { |number| number * 2 }\nend\n\nAdd = -\u003e params do\n  a, b = Kind::Hash.value_or_empty(params).values_at(:a, :b)\n\n  return Kind::None unless Kind::Numeric?(a, b)\n\n  Kind::Some(a + b)\nend\n\n# --\n\nAdd.call(1)    # #\u003cKind::Maybe::None:0x0000... @value=nil\u003e\nAdd.call({})   # #\u003cKind::Maybe::None:0x0000... @value=nil\u003e\nAdd.call(a: 1) # #\u003cKind::Maybe::None:0x0000... @value=nil\u003e\nAdd.call(b: 2) # #\u003cKind::Maybe::None:0x0000... @value=nil\u003e\n\nAdd.call(a:1, b: 2) # #\u003cKind::Maybe::Some:0x0000... @value=3\u003e\n\n# --\n\nKind::Maybe[a: 1, b: 2].then(\u0026Add).value_or(0) # 3\n\nKind::Maybe[1].then(\u0026Add).value_or(0) # 0\n\n# --\n\nAdd.(a: 2, b: 2).then(\u0026Double).value # 8\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Optional\n\nThe `Kind::Optional` constant is an alias for `Kind::Maybe`. e.g:\n\n```ruby\nresult1 =\n  Kind::Optional\n    .new(5)\n    .map { |value| value * 5 }\n    .map { |value| value - 10 }\n    .value_or(0)\n\nputs result1 # 15\n\n# ---\n\nresult2 =\n  Kind::Optional[5]\n    .then { |value| value * 5 }\n    .then { |value| value + 10 }\n    .value_or { 0 }\n\nputs result2 # 35\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Replacing blocks by lambdas\n\n```ruby\nDouble = -\u003e(arg) do\n  number = Kind::Numeric.or_nil(arg)\n\n  Kind::Maybe[number].then { |number| number * 2 }\nend\n\n# --\n\nKind::Optional[2].then(\u0026Double).value_or(0) # 4\n\n# --\n\nKind::Optional['2'].then(\u0026Double).value_or(0) # 0\nKind::Optional[nil].then(\u0026Double).value_or(0) # 0\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe(\u003cType\u003e)\n\nYou can use `Kind::Maybe(\u003cType\u003e)` or `Kind::Optional(\u003cType\u003e)` to create a maybe monad which will return None if the given input hasn't the expected type. e.g:\n\n```ruby\nresult1 =\n  Kind::Maybe(Numeric)\n    .wrap(5)\n    .then { |value| value * 5 }\n    .value_or { 0 }\n\nputs result1 # 25\n\n# ---\n\nresult2 =\n  Kind::Optional(Numeric)\n    .wrap('5')\n    .then { |value| value * 5 }\n    .value_or { 0 }\n\nputs result2 # 0\n```\n\nThis typed maybe has the same methods of `Kind::Maybe` class. e.g:\n\n```ruby\nKind::Maybe(Numeric)[5]\nKind::Maybe(Numeric).new(5)\nKind::Maybe(Numeric).wrap(5)\n\n# ---\n\nKind::Optional(Numeric)[5]\nKind::Optional(Numeric).new(5)\nKind::Optional(Numeric).wrap(5)\n```\n\n#### Real world examples\n\nIt is very common the need to avoid some operation when a method receives the wrong input.\nIn these scenarios, you could create a maybe monad that will return None if the given input hasn't the expected type. e.g:\n\n```ruby\ndef person_name(params)\n  Kind::Maybe(Hash)\n    .wrap(params)\n    .then  { |hash| hash.values_at(:first_name, :last_name) }\n    .then  { |names| names.map(\u0026Kind::Presence).tap(\u0026:compact!) }\n    .check { |names| names.size == 2 }\n    .then  { |(first_name, last_name)| \"#{first_name} #{last_name}\" }\n    .value_or { 'John Doe' }\nend\n\nperson_name('')   # \"John Doe\"\nperson_name(nil)  # \"John Doe\"\n\nperson_name(first_name: 'Rodrigo')   # \"John Doe\"\nperson_name(last_name: 'Serradura')  # \"John Doe\"\n\nperson_name(first_name: 'Rodrigo', last_name: 'Serradura') # \"Rodrigo Serradura\"\n\n#\n# See below the previous implementation without using the maybe monad.\n#\ndef person_name(params)\n  default = 'John Doe'\n\n  return default unless params.kind_of?(Hash)\n\n  names = params.values_at(:first_name, :last_name).map(\u0026Kind::Presence).tap(\u0026:compact!)\n\n  return default if names.size != 2\n\n  first_name, last_name = names\n\n  \"#{first_name} #{last_name}\"\nend\n```\n\nTo finish follows an example of how to use the Maybe monad to handle arguments in coupled methods.\n\n```ruby\nmodule PersonIntroduction1\n  extend self\n\n  def call(params)\n    optional = Kind::Maybe(Hash).wrap(params)\n\n    \"Hi my name is #{full_name(optional)}, I'm #{age(optional)} years old.\"\n  end\n\n  private\n\n    def full_name(optional)\n      optional.map { |hash| \"#{hash[:first_name]} #{hash[:last_name]}\".strip }\n              .presence\n              .value_or { 'John Doe' }\n    end\n\n    def age(optional)\n      optional.dig(:age).value_or(0)\n    end\nend\n\n#\n# See below the previous implementation without using an optional.\n#\nmodule PersonIntroduction2\n  extend self\n\n  def call(params)\n    \"Hi my name is #{full_name(params)}, I'm #{age(params)} years old.\"\n  end\n\n  private\n\n    def full_name(params)\n      default = 'John Doe'\n\n      case params\n      when Hash then\n        Kind::Presence.(\"#{params[:first_name]} #{params[:last_name]}\".strip) || default\n      else default\n      end\n    end\n\n    def age(params)\n      case params\n      when Hash then params.fetch(:age, 0)\n      else 0\n      end\n    end\nend\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Error handling\n\n#### Kind::Maybe.wrap {}\n\nThe `Kind::Maybe#wrap` can receive a block, and if an exception (at `StandardError level`) happening, this will generate a None result.\n\n```ruby\nKind::Maybe(Numeric)\n  .wrap { 2 / 0 } # #\u003cKind::Maybe::None:0x0000... @value=#\u003cZeroDivisionError: divided by 0\u003e\u003e\n\nKind::Maybe(Numeric)\n  .wrap(2) { |number| number / 0 } # #\u003cKind::Maybe::None:0x0000... @value=#\u003cZeroDivisionError: divided by 0\u003e\u003e\n```\n\n#### Kind::Maybe.map! or Kind::Maybe.then!\n\nBy default the `Kind::Maybe#map` and `Kind::Maybe#then` intercept exceptions at the `StandardError` level. So if an exception was intercepted a None will be returned.\n\n```ruby\n# Handling StandardError exceptions\nresult1 = Kind::Maybe[2].map { |number| number / 0 }\nresult1.none? # true\nresult1.value # #\u003cZeroDivisionError: divided by 0\u003e\n\nresult2 = Kind::Maybe[3].then { |number| number / 0 }\nresult2.none? # true\nresult2.value # #\u003cZeroDivisionError: divided by 0\u003e\n```\n\nBut there are versions of these methods (`Kind::Maybe#map!` and `Kind::Maybe#then!`) that allow the exception leak, so, the user must handle the exception by himself or use this method when he wants to see the error be raised.\n\n```ruby\n# Leaking StandardError exceptions\nKind::Maybe[2].map! { |number| number / 0 } # ZeroDivisionError (divided by 0)\n\nKind::Maybe[2].then! { |number| number / 0 } # ZeroDivisionError (divided by 0)\n```\n\n\u003e **Note:** If an exception (at StandardError level) is returned by the methods `#then`, `#map` it will be resolved as None.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe#try\n\nIf you don't want to use `#map/#then` to access the value, you could use the `#try` method to access it. So, if the value wasn't `nil` or `Kind::Undefined`, the some monad will be returned.\n\n```ruby\nobject = 'foo'\n\nKind::Maybe[object].try(:upcase).value # \"FOO\"\n\nKind::Maybe[{}].try(:fetch, :number, 0).value # 0\n\nKind::Maybe[{number: 1}].try(:fetch, :number).value # 1\n\nKind::Maybe[object].try { |value| value.upcase }.value # \"FOO\"\n\n#############\n# Nil value #\n#############\n\nobject = nil\n\nKind::Maybe[object].try(:upcase).value # nil\n\nKind::Maybe[object].try { |value| value.upcase }.value # nil\n\n#########################\n# Kind::Undefined value #\n#########################\n\nobject = Kind::Undefined\n\nKind::Maybe[object].try(:upcase).value # nil\n\nKind::Maybe[object].try { |value| value.upcase }.value # nil\n```\n\n\u003e **Note:** You can use the `#try` method with `Kind::Optional` objects.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe#try!\n\nHas the same behavior of its `#try`, but it will raise an error if the value doesn't respond to the expected method.\n\n```ruby\nKind::Maybe[{}].try(:upcase)  # =\u003e #\u003cKind::Maybe::None:0x0000... @value=nil\u003e\n\nKind::Maybe[{}].try!(:upcase) # =\u003e NoMethodError (undefined method `upcase' for {}:Hash)\n```\n\n\u003e **Note:** You can also use the `#try!` method with `Kind::Optional` objects.\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe#dig\n\nHas the same behavior of Ruby dig methods ([Hash](https://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig), [Array](https://ruby-doc.org/core-2.3.0/Array.html#method-i-dig), [Struct](https://ruby-doc.org/core-2.3.0/Struct.html#method-i-dig), [OpenStruct](https://ruby-doc.org/stdlib-2.3.0/libdoc/ostruct/rdoc/OpenStruct.html#method-i-dig)), but it will not raise an error if some value can't be digged.\n\n```ruby\n[nil, 1, '', /x/].each do |value|\n  p Kind::Maybe[value].dig(:foo).value # nil\nend\n\n# --\n\na = [1, 2, 3]\n\nKind::Maybe[a].dig(0).value # 1\n\nKind::Maybe[a].dig(3).value # nil\n\n# --\n\nh = { foo: {bar: {baz: 1}}}\n\nKind::Maybe[h].dig(:foo).value             # {bar: {baz: 1}}\nKind::Maybe[h].dig(:foo, :bar).value       # {baz: 1}\nKind::Maybe[h].dig(:foo, :bar, :baz).value # 1\n\nKind::Maybe[h].dig(:foo, :bar, 'baz').value # nil\n\n# --\n\ni = { foo: [{'bar' =\u003e [1, 2]}, {baz: [3, 4]}] }\n\nKind::Maybe[i].dig(:foo, 0, 'bar', 0).value # 1\nKind::Maybe[i].dig(:foo, 0, 'bar', 1).value # 2\nKind::Maybe[i].dig(:foo, 0, 'bar', -1).value # 2\n\nKind::Maybe[i].dig(:foo, 0, 'bar', 2).value # nil\n\n# --\n\ns = Struct.new(:a, :b).new(101, 102)\no = OpenStruct.new(c: 103, d: 104)\nb = { struct: s, ostruct: o, data: [s, o]}\n\nKind::Maybe[s].dig(:a).value            # 101\nKind::Maybe[b].dig(:struct, :b).value   # 102\nKind::Maybe[b].dig(:data, 0, :b).value  # 102\nKind::Maybe[b].dig(:data, 0, 'b').value # 102\n\nKind::Maybe[o].dig(:c).value            # 103\nKind::Maybe[b].dig(:ostruct, :d).value  # 104\nKind::Maybe[b].dig(:data, 1, :d).value  # 104\nKind::Maybe[b].dig(:data, 1, 'd').value # 104\n\nKind::Maybe[s].dig(:f).value           # nil\nKind::Maybe[o].dig(:f).value           # nil\nKind::Maybe[b].dig(:struct, :f).value  # nil\nKind::Maybe[b].dig(:ostruct, :f).value # nil\nKind::Maybe[b].dig(:data, 0, :f).value # nil\nKind::Maybe[b].dig(:data, 1, :f).value # nil\n```\n\n\u003e **Note:** You can also use the `#dig` method with `Kind::Optional` objects.\n\n### Kind::Maybe#check\n\nThis method will return the current Some after verify if the block output is truthy. e.g:\n\n```ruby\nKind::Maybe(Array)\n  .wrap(['Rodrigo', 'Serradura'])\n  .then  { |names| names.map(\u0026Kind::Presence).tap(\u0026:compact!) }\n  .check { |names| names.size == 2 }\n  .value # [\"Rodrigo\", \"Serradura\"]\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Kind::Maybe#presence\n\nThis method will return None if the wrapped value wasn't present.\n\n```ruby\nresult = Kind::Maybe(Hash).wrap(foo: '').dig(:foo).presence\nresult.none? # true\nresult.value # nil\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Kind::Empty\n\nWhen you define a method that has default arguments, for certain data types, you will always create a new object in memory. e.g:\n\n```ruby\ndef something(params = {})\n  params.object_id\nend\n\nputs something # 70312470300460\nputs something # 70312470295800\nputs something # 70312470278400\nputs something # 70312470273800\n```\n\nSo, to avoid an unnecessary allocation in memory, the `kind` gem exposes some frozen objects to be used as default values.\n\n- `Kind::Empty::SET`\n- `Kind::Empty::HASH`\n- `Kind::Empty::ARRAY`\n- `Kind::Empty::STRING`\n\nUsage example:\n\n```ruby\ndef do_something(value, with_options: Kind::Empty::HASH)\n  # ...\nend\n```\n\n### Defining Empty as Kind::Empty an alias\n\nYou can require `kind/empty/constant` to define `Empty` as a `Kind::Empty` alias. But, a `LoadError` will be raised if there is an already defined constant `Empty`.\n\nSo if you required this file, the previous example could be written like this:\n\n```ruby\ndef do_something(value, with_options: Empty::HASH)\n  # ...\nend\n```\n\nFollows the list of constants if the alias was defined:\n\n- `Empty::SET`\n- `Empty::HASH`\n- `Empty::ARRAY`\n- `Empty::STRING`\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Kind::Validator (ActiveModel::Validations)\n\nThis module enables the capability to validate types via [`ActiveModel::Validations \u003e= 3.2, \u003c 7.0`](https://api.rubyonrails.org/classes/ActiveModel/Validations.html). e.g\n\n```ruby\nclass Person\n  include ActiveModel::Validations\n\n  attr_accessor :first_name, :last_name\n\n  validates :first_name, :last_name, kind: String\nend\n```\n\nAnd to make use of it, you will need to do an explicitly require. e.g:\n\n```ruby\n# In some Gemfile\ngem 'kind', require: 'kind/validator'\n\n# In some .rb file\nrequire 'kind/validator'\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Usage\n\n#### [Object#===](https://ruby-doc.org/core-3.0.0/Object.html#method-i-3D-3D-3D)\n\n```ruby\nvalidates :name, kind: { of: String }\n```\n\nUse an array to verify if the attribute is an instance of one of the classes/modules.\n\n```ruby\nvalidates :status, kind: { of: [String, Symbol]}\n```\n\nBecause of kind verification be made via `===` you can use type handlers as the expected kinds.\n\n```ruby\nvalidates :alive, kind: Kind::Boolean\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### [Kind.is](#verifying-the-kind-of-some-classmodule)\n\n```ruby\n#\n# Verifying if the attribute value is the class or a subclass.\n#\nclass Human; end\nclass Person \u003c Human; end\nclass User \u003c Human; end\n\nvalidates :human_kind, kind: { is: Human }\n\n#\n# Verifying if the attribute value is the module or if it is a class that includes the module\n#\nmodule Human; end\nclass Person; include Human; end\nclass User; include Human; end\n\nvalidates :human_kind, kind: { is: Human }\n\n#\n# Verifying if the attribute value is the module or if it is a module that extends the module\n#\nmodule Human; end\nmodule Person; extend Human; end\nmodule User; extend Human; end\n\nvalidates :human_kind, kind: { is: Human }\n\n# or use an array to verify if the attribute\n# is a kind of one those classes/modules.\n\nvalidates :human_kind, kind: { is: [Person, User] }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### [Object#instance_of?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-instance_of-3F)\n\n```ruby\nvalidates :name, kind: { instance_of: String }\n\n# or use an array to verify if the attribute\n# is an instance of one of the classes/modules.\n\nvalidates :name, kind: { instance_of: [String, Symbol] }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### [Object#respond_to?](https://ruby-doc.org/core-3.0.0/Object.html#method-i-respond_to-3F)\n\n```ruby\nvalidates :handler, kind: { respond_to: :call }\n```\n\nThis validation can verify one or multiple methods.\n\n```ruby\nvalidates :params, kind: { respond_to: [:[], :values_at] }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Array.new.all? { |item| item.kind_of?(Class) }\n\n```ruby\nvalidates :account_types, kind: { array_of: String }\n\n# or use an array to verify if the attribute\n# is an instance of one of the classes\n\nvalidates :account_types, kind: { array_of: [String, Symbol] }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n#### Array.new.all? { |item| expected_values.include?(item) }\n\n```ruby\n# Verifies if the attribute value\n# is an array with some or all the expected values.\n\nvalidates :account_types, kind: { array_with: ['foo', 'bar'] }\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Defining the default validation strategy\n\nBy default, you can define the attribute type directly (without a hash). e.g.\n\n```ruby\nvalidates :name, kind: String\n# or\nvalidates :name, kind: [String, Symbol]\n```\n\nTo changes this behavior you can set another strategy to validates the attributes types:\n\n```ruby\nKind::Validator.default_strategy = :instance_of\n\n# Tip: Create an initializer if you are in a Rails application.\n```\n\nAnd these are the available options to define the default strategy:\n-  `kind_of` *(default)*\n-  `instance_of`\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n### Using the `allow_nil` and `strict` options\n\nYou can use the `allow_nil` option with any of the kind validations. e.g.\n\n```ruby\nvalidates :name, kind: String, allow_nil: true\n```\n\nAnd as any active model validation, kind validations works with the `strict: true`\noption and with the `validates!` method. e.g.\n\n```ruby\nvalidates :first_name, kind: String, strict: true\n# or\nvalidates! :last_name, kind: String\n```\n\n[⬆️ \u0026nbsp;Back to Top](#table-of-contents-)\n\n## Similar Projects\n\n- [dry-types](https://dry-rb.org/gems/dry-types)\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/serradura/kind. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/serradura/kind/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Kind project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/serradura/kind/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fkind","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserradura%2Fkind","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserradura%2Fkind/lists"}