{"id":46834127,"url":"https://github.com/low-rb/low_type","last_synced_at":"2026-03-10T11:12:40.665Z","repository":{"id":325550053,"uuid":"1099442604","full_name":"low-rb/low_type","owner":"low-rb","description":"Elegant types in Ruby","archived":false,"fork":false,"pushed_at":"2026-03-04T12:16:11.000Z","size":302,"stargazers_count":333,"open_issues_count":8,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-04T19:29:24.915Z","etag":null,"topics":["ruby","sinatra","typechecker"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/low-rb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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":null,"dco":null,"cla":null}},"created_at":"2025-11-19T02:03:03.000Z","updated_at":"2026-03-04T14:11:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/low-rb/low_type","commit_stats":null,"previous_names":["low-rb/low_type"],"tags_count":44,"template":false,"template_full_name":null,"purl":"pkg:github/low-rb/low_type","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/low-rb%2Flow_type","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/low-rb%2Flow_type/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/low-rb%2Flow_type/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/low-rb%2Flow_type/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/low-rb","download_url":"https://codeload.github.com/low-rb/low_type/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/low-rb%2Flow_type/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30242403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T00:58:18.660Z","status":"online","status_checked_at":"2026-03-08T02:00:06.215Z","response_time":56,"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":["ruby","sinatra","typechecker"],"created_at":"2026-03-10T11:12:39.729Z","updated_at":"2026-03-10T11:12:40.656Z","avatar_url":"https://github.com/low-rb.png","language":"Ruby","readme":"\u003ca href=\"https://rubygems.org/gems/low_type\" title=\"Install gem\"\u003e\u003cimg src=\"https://badge.fury.io/rb/low_type.svg\" alt=\"Gem version\" height=\"18\"\u003e\u003c/a\u003e \u003ca href=\"https://github.com/low-rb/low_type\" title=\"GitHub\"\u003e\u003cimg src=\"https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge\u0026logo=github\u0026logoColor=white\" alt=\"GitHub repo\" height=\"18\"\u003e\u003c/a\u003e \u003ca href=\"https://codeberg.org/Iow/type\" title=\"Codeberg\"\u003e\u003cimg src=\"https://img.shields.io/badge/Codeberg-2185D0?style=for-the-badge\u0026logo=Codeberg\u0026logoColor=white\" alt=\"Codeberg repo\" height=\"18\"\u003e\u003c/a\u003e\n\n# LowType\n\nLowType introduces the concept of \"type expressions\" in method arguments. When an argument's default value resolves to a type instead of a value then it's treated as a type expression. Now you can have types in Ruby in the simplest syntax possible:\n\n```ruby\nclass MyClass\n  include LowType\n\n  def say_hello(greeting: String)\n    # Raises exception at runtime if greeting is not a String.\n    # Or `config.type_checking = false` to annotate your code.\n  end\nend\n```\n\n## Default values\n\nPlace `|` after the type definition to provide a default value when the argument is `nil`:\n```ruby\ndef say_hello(greeting = String | 'Hello')\n  puts greeting\nend\n```\n\nOr with keyword arguments:\n```ruby\ndef say_hello(greeting: String | 'Hello')\n  puts greeting\nend\n```\n\n## Enumerables\n\nWrap your type in an `Array[T]` or `Hash[T]` enumerable type. An `Array` of `String`s looks like:\n```ruby\ndef say_hello(greetings: Array[String])\n  greetings # =\u003e ['Hello', 'Howdy', 'Hey']\nend\n```\n\nRepresent a `Hash` with `key =\u003e value` syntax:\n```ruby\ndef say_hello(greetings: Hash[String =\u003e Integer])\n  greetings # =\u003e {'Hello' =\u003e 123, 'Howdy' =\u003e 456, 'Hey' =\u003e 789})\nend\n```\n\n## Return values\n\nAfter your method's parameters add `-\u003e { T }` to define a return value:\n```ruby\ndef say_hello() -\u003e { String }\n  'Hello' # Raises exception if the returned value is not a String.\nend\n```\n\nReturn values can also be defined as `nil`able:\n```ruby\ndef say_hello(greetings: Array[String]) -\u003e { String | nil }\n  return nil if greetings.first == 'Goodbye'\n  greetings.first\nend\n```\n\nIf you need a multi-line return type/value then I'll even let you put the `-\u003e { T }` on multiple lines, okay? I won't judge. You are a unique flower 🌸 with your own style, your own needs. You have purpose in this world and though you may never find it, your loved ones will cherish knowing you and wish you were never gone:\n```ruby\ndef say_farewell_with_a_long_method_name(farewell: String)\n  -\u003e {\n    ::Long::Name::Space::CustomClassOne | ::Long::Name::Space::CustomClassTwo | ::Long::Name::Space::CustomClassThree\n  }\n\n  # Code that returns an instance of one of the above types.\nend\n```\n\n## Instance variables\n\nTo define typed `@instance` variables use the `type_[reader, writer, accessor]` methods.  \nThese replicate `attr_[reader, writer, accessor]` methods but also allow you to define and check types.\n\n### Type Reader\n\n```ruby\ntype_reader name: String # Creates a public method called `name` that gets the value of @name\nname # Get the value with type checking\n\ntype_reader name: String | 'Cher' # Gets the value of @name with a default value if it's `nil`\nname # Get the value with type checking and return 'Cher' if the value is `nil`\n```\n\n### Type Writer\n\n```ruby\ntype_writer name: String # Creates a public method called `name=(arg)` that sets the value of @name\nname = 'Tim' # Set the value with type checking\n```\n\n### Type Accessor\n\n```ruby\ntype_accessor name: String # Creates public methods to get or set the value of @name\nname # Get the value with type checking\nname = 'Tim' # Set the value with type checking\n\ntype_accessor name: String | 'Cher' # Get/set the value of @name with a default value if it's `nil`\nname # Get the value with type checking and return 'Cher' if the value is `nil`\nname = 'Tim' # Set the value with type checking\n```\n\n### ℹ️ Multiple Arguments\n\nYou can define multiple typed accessor methods just like you would with `attr_[reader, writer, accessor]`:\n```ruby\ntype_accessor name: String | nil, occupation: 'Doctor', age: Integer | 33\nname # =\u003e nil\noccupation # =\u003e Doctor (not type checked)\nage = 'old' # =\u003e Raises ArgumentTypeError\nage # =\u003e 33\n```\n\n## Local variables\n\n### `type()`\n\n*alias: `low_type()`*\n\nTo define typed `local` variables at runtime use the `type()` method:\n```ruby\nmy_var = type MyType | fetch_my_object(id: 123)\n```\n\n`my_var` is now type checked to be of type `MyType` when assigned to.\n\nDon't forget that these are just Ruby expressions and you can do more conditional logic as long as the last expression evaluates to a value:\n```ruby\nmy_var = type String | (say_goodbye || 'Hello Again')\n```\n\n## Syntax\n\n### `[T]` Enumerables\n\n`Array[T]` and `Hash[T]` class methods represent enumerables in the context of type expressions. If you need to create a new `Array`/`Hash` then use `Array.new()`/`Hash.new()` or Array and Hash literals `[]` and `{}`. This is the same syntax that [RBS](https://github.com/ruby/rbs) uses and we need to get use to these class methods returning type expressions if we're ever going to have inline types in Ruby. [RuboCop](https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/HashConversion) also suggests `{}` over `Hash[]` syntax for creating hashes.\n\nℹ️ **Note:** To use the `Array[]`/`Hash[]` enumerable syntax with `type()` you must add `using LowType::Syntax` when including LowType:\n```ruby\ninclude LowType\nusing LowType::Syntax\n```\n\n### `|` Union Types / Default Value\n\nThe pipe symbol (`|`) is used in the context of type expressions to define multiple types as well as provide the default value:\n- To allow multiple types separate them between pipes: `my_var = TypeOne | TypeTwo`\n- The last *value*/`nil` defined becomes the default value: `my_var = TypeOne | TypeTwo | nil`\n\nℹ️ **Note:** If no default value is defined then the argument will be required.\n\n### `-\u003e { T }` Return Type\n\nThe `-\u003e { T }` syntax is a lambda without an assignment to a local variable. This is valid Ruby that can be placed immediately after a method definition and on the same line as the method definition, to visually look like the output of that method. It's inert and doesn't run when the method is called, similar to how default values are never called if the argument is managed by LowType. Pretty cool stuff yeah? Your type expressions won't keep re-evaluating in the wild 🐴, only on class load.\n\nℹ️ **Note:** A method that takes no arguments must include empty parameters `()` for the `-\u003e { T }` syntax to be valid; `def method() -\u003e { T }`.\n\n### `value(T)` Value Expression\n\n*alias: `low_value()`*\n\nTo treat a type as if it were a value, pass it through `value()` first:\n```ruby\ndef my_method(my_arg: String | MyType | value(MyType)) # =\u003e MyType is the default value\n```\n\n## Performance\n\nLowType evaluates type expressions on *class load* (just once) to be efficient and thread-safe. Then the defined types are checked per method call.  \nHowever, `type()` type expressions are evaluated when they are called at *runtime* on an instance, and this may impact performance.\n\n|                         | **Evaluation**  | **Validation** | ℹ️ *Example*            |\n|-------------------------|-----------------|----------------|-------------------------|\n| **Method param types**  | 🟢 Class load   | 🟠 Runtime     | `def method(name: T)`   |\n| **Method return types** | 🟢 Class load   | 🟠 Runtime     | `def method() -\u003e { T }` |\n| **Instance types**      | 🟢 Class load   | 🟠 Runtime     | `type_accessor(name: T)`|\n| **Local types**         | 🟠 Runtime      | 🟠 Runtime     | `type(T)`               |\n\n## Scope\n\nLowType only affects the class that it's `include`d into. Class methods `Array[]`/`Hash[]` are modified for the type expression enumerable syntax (`[]`) to work, but only for LowType's internals (using refinements) and not the `include`d class. The `type()` method requires `using LowType::Syntax` if you want to use the enumerable syntax and will affect all `Array[]`/`Hash[]` class methods of the `include`d class.\n\n## Config\n\nCopy and paste the following and change the defaults to configure LowType:\n\n```ruby\n# This configuration should be set before the class that includes LowType is required.\nLowType.configure do |config|\n  # Set to \"false\" to disable type checking, which you may like to do in a production environment for example.\n  # There will still be a shim method to convert typed args to untyped args but performance will be near 100%.\n  config.type_checking = true\n\n  # Set to :log to log instead of raising of an exception when a type is invalid. [UNRELEASED]\n  config.error_mode = :error\n  config.error_callback = nil # Or a lambda like \"-\u003e (error) { MyLogger.log(error) }\"\n\n  # Set to :value to show a concatenated inspect of the invalid param when an error is raised. Or :none to redact.\n  # Great for debugging, bad for security, and makes tests harder to write when the error messages are so dynamic.\n  config.output_mode = :type\n  config.output_size = 100\n\n  # Set to \"false\" to type check only the first element of an Array/Hash (performance vs accuracy).\n  config.deep_type_check = true\n\n  # The \"|\" pipe syntax requires a monkey-patch but can be disabled if you don't need union types with default values.\n  # This is the only monkey-patch in the entire library and is a relatively harmless one, see \"syntax/union_types.rb\".\n  # Set to false and typed params will always be required, as there's no \"| nil\" syntax (remove type to make optional)\n  config.union_type_expressions = true\nend\n```\n\n## Types\n\n### Basic types\n\n- `String`\n- `Integer`\n- `Float`\n- `Array`\n- `Hash`\n- `nil` represents an optional value\n\nℹ️ **Note:** Any class/type that's available to Ruby is available to LowType, `require` it and specify its full namespace.\n\n### Complex types\n\n- `Boolean` - Accepts `true`/`false`) [UNRELEASED]\n- `Enum` - Usage: `Enum[1, 2, 3]` [[CONCEPT STAGE](https://github.com/low-rb/low_type/issues/6)]\n- `Tuple` (subclass of `Array`)\n- `Status` (subclass of `Integer`)\n- `Headers` (subclass of `Hash`)\n- `HTML` (subclass of `String`) - TODO: Check that string is HTML\n- `JSON` (subclass of `String`) - TODO: Check that string is JSON\n- `XML` (subclass of `String`) - TODO: Check that string is XML\n\n## Integrations\n\nBecause LowType is low-level it should work with method definitions in any framework out of the box. With that in mind we go a little further here at free-software-by-shadowy-figure-co to give you that extra framework-specific-special-feeling:\n\n### Sinatra\n\n`include LowType` in your modular `Sinatra::Base` subclass to get Sinatra specific return types.  \nLowType will automatically add the necessary `content_type` [UNRELEASED] and type check the return value:\n\n```ruby\nrequire 'sinatra/base'\nrequire 'low_type'\n\nclass MyApp \u003c Sinatra::Base\n  include LowType\n\n  # A simple string response type.\n  get '/' do -\u003e { String }\n    'body'\n  end\n\n  # Standard types Sinatra uses.\n  get '/' do -\u003e { Array[Integer, Hash, String] }\n    [200, {}, '\u003ch1\u003eHello!\u003c/h1\u003e']    \n  end\n\n  # Specific types for Sinatra.\n  get '/' do -\u003e { Tuple[Status, Headers, HTML] }\n    [200, {}, '\u003ch1\u003eHello!\u003c/h1\u003e']    \n  end\nend\n```\n\n### LowDependency\n\nWith [LowDependency](https://github.com/low-rb/low_dependency) you can inject your dependencies automatically via the constructor:\n```ruby\nclass MyClass\n  include LowType\n\n  def initialize(my_dependency: Dependency)\n    @my_dependency = my_dependency # =\u003e The dependency is injected.\n  end\nend\n```\n\n### Rubocop\n\nBecause we're living in the future, Rubocop isn't ready for us. Put the following in your `.rubocop.yml`:\n\n```yaml\n# Support LowType return value \"-\u003e { T }\" syntax.\nStyle/TrailingBodyOnMethodDefinition:\n  Enabled: false\nLayout/IndentationConsistency:\n  Enabled: false\nLayout/MultilineBlockLayout:\n  Enabled: false\nStyle/DefWithParentheses:\n  Enabled: false\nLint/Void:\n  Enabled: false\n\n# Support Array[]/Hash[] syntax.\nStyle/RedundantArrayConstructor:\n  Enabled: false\n```\n\n## Installation\n\nAdd `gem 'low_type'` to your Gemfile then:\n```\nbundle install\n```\n\n## Advanced Techniques\n\n### Inheritance\n\nYou must `include LowType` in every class that you'd like to have type checking/annotations for.  \nHowever you can eliminate the need for this `include` in every child class of a parent via the `inherited` hook:\n```ruby\nclass Parent\n  def self.inherited(child)\n    child.include LowType\n  end\nend\n\nclass Child \u003c Parent\n  # LowType available here.\nend\n```\n\n## Architecture\n\n```mermaid\nsequenceDiagram\n  box File Load\n    participant Lowkey\n    participant Proxies@{ \"type\" : \"collections\" }\n  end\n\n  box Class Load\n    participant Expressions@{ \"type\" : \"collections\" }\n    participant LowType\n  end\n\n  box Runtime\n    participant Methods@{ \"type\" : \"collections\" }\n  end\n\n  Lowkey-\u003e\u003eProxies: Parses AST\n  Proxies-\u003e\u003eExpressions: Stores\n  LowType-\u003e\u003eExpressions: Evaluates\n  LowType-\u003e\u003eMethods: Redefines\n  Methods--\u003e\u003eExpressions: Validates per method call or disable per environment\n```\n\nThree distinct phases isolate concerns:\n1. **File Load:** Code is parsed into an Abstract Syntax Tree but not evaluated (stored as strings)\n2. **Class Load:** Constants and expressions are evaluated into real Ruby objects and methods redefined\n3. **Runtime:** Method argument types and return types are optionally validated\n\n## Philosophy\n\n🦆 **Duck typing is beautiful.** Ruby is an amazing language **BECAUSE** it's not typed. I don't believe Ruby should ever be fully typed, but you should be able to sprinkle in types into some areas of your codebase where you'd like self-documentation and a little reassurance that the right values are coming in/out.\n\n🌀 **Less DSL. More types.** As much as possible LowType looks just like Ruby if it had types. There's no special method calls for the base functionality, and defining types at runtime simply uses a `type()` method which almost looks like a `type` keyword, had Ruby implemented types.\n\n💡 **Dedicated to David.** A kind granddad and an electrical engineer who I'll never be as smart as. Love you David.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flow-rb%2Flow_type","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flow-rb%2Flow_type","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flow-rb%2Flow_type/lists"}