{"id":19198996,"url":"https://github.com/witchcrafters/type_class","last_synced_at":"2025-04-05T14:09:19.021Z","repository":{"id":11571545,"uuid":"70052387","full_name":"witchcrafters/type_class","owner":"witchcrafters","description":"(Semi-)principled type classes for Elixir","archived":false,"fork":false,"pushed_at":"2022-06-16T09:30:52.000Z","size":264,"stargazers_count":137,"open_issues_count":8,"forks_count":16,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-29T13:11:23.668Z","etag":null,"topics":["elixir","hierarchy","macros","protocol","typeclass"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/type_class","language":"Elixir","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/witchcrafters.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-05T11:01:05.000Z","updated_at":"2025-03-04T17:25:05.000Z","dependencies_parsed_at":"2022-09-15T23:21:46.371Z","dependency_job_id":null,"html_url":"https://github.com/witchcrafters/type_class","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/witchcrafters%2Ftype_class","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/witchcrafters%2Ftype_class/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/witchcrafters%2Ftype_class/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/witchcrafters%2Ftype_class/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/witchcrafters","download_url":"https://codeload.github.com/witchcrafters/type_class/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247345854,"owners_count":20924102,"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":["elixir","hierarchy","macros","protocol","typeclass"],"created_at":"2024-11-09T12:25:15.239Z","updated_at":"2025-04-05T14:09:18.994Z","avatar_url":"https://github.com/witchcrafters.png","language":"Elixir","readme":"![](https://raw.githubusercontent.com/expede/type_class/master/brand/logo.png)\n\n`TypeClass` brings (semi-)[principled](http://degoes.net/articles/principled-typeclasses) [type classes](https://en.wikibooks.org/wiki/Haskell/Classes_and_types) to Elixir\n\n# README\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Type Classes](#type-classes)\n  - [Condensed Style](#condensed-style)\n  - [Hierarchical](#hierarchical)\n  - [Principled](#principled)\n- [Escape Hatches](#escape-hatches)\n- [Example](#example)\n  - [TypeClass](#typeclass)\n  - [Haskell](#haskell)\n\n## Quick Start\n\n```elixir\ndef deps do\n  [{:type_class, \"~\u003e 1.1\"}]\nend\n```\n\n## Type Classes\nType classes are not unlike protocols. They are essentially a mechanism for ad hoc polymorphism. However, doing extensive work with protocols can be cumbersome in Elixir. Even the standard library uses the [`Enumerable`](https://hexdocs.pm/elixir/Enumerable.html) protocol to support the `Enum` module. `TypeClass` attempts to hide many of the details to give you a single module interface.\n\n### Condensed Style\nTo this end, `TypeClass` provides the [`defclass/2`](TypeClass.html#defclass/2) macro to handle generating all of the modules, submodules, and protocols.\n\n[`definst/3`](TypeClass.html#definst/3) (and [`definst/2`](TypeClass.html#definst/2)) is very similar to `defimpl/3`, except that you don't need to pass it the actual protocol; you only pass it just the \"top\" class module. It will also automatically run a number of checks at compile time to help keep everything running as per the definition in [`defclass/2`](TypeClass.html#defclass/2) (more on that later).\n\n### Hierarchical\nType classes can be hierarchical. The [`extend/2`](TypeClass.Dependency.html#extend/2) macro allows defining another class that your class depends on existing. A common and similar example from Haskell is how the monad instance must also be an applicative, which in turn must be a functor. [`definst/3`](TypeClass.html#definst/3) will check that the type you are implementing already has an implementation of the parent classes. Specifying multiple parents is totally okay, as this is superclassing, not subclassing like in an object oriented system.\n\n### Principled\nType classes have the ability to be abused. For instance, in languages such as Haskell, a programmer can define an instance of `Monad a` that is not actually a monad. This can lead to confusing and unexpected behaviour. After all the purpose of protocols and type classes is so that we abstract some invariant behaviour over many data types.\n\nAt the core, type classes are about the _properties_ that enable its functions to work correctly. To emphasize that: _properties are the most important part of a type class_. Strictly speaking, for the compiler to enforce properties at compile time, it needs to have a lot of type-level information (ideally dependent types, GADTs, or very advanced static analysis). Elixir is dynamically typed, and has almost no type information at compile time.\n\n`TypeClass` meets this challenge halfway: property testing. [`definst/3`](TypeClass.html#definst/3) will property test a small batch of examples on every data type that the class is defined for _at compile time_. By default, it skips this check in production, runs a minimal set of cases in development, and runs a larger suite in the test environment. Property testing lets `TypeClass` check hundreds of specific examples very quickly, so while it doesn't give you a guarantee that your instance is correct, it does give you a high level of confidence.\n\n[John De Goes](http://degoes.net) defines [principled type classes](http://degoes.net/articles/principled-typeclasses) as:\n\n\u003e Haskell-style. A baked-in notion of type classes in the overall style of Haskell, Purescript, Idris, etc.\n\n[`defclass/2`](TypeClass.html#defclass/2) and [`definst/3`](TypeClass.html#definst/3) get us 99% of the way here. It's not as lightweight as in Haskell \u0026c, but it's close (and much more succinct than what is available in `Kernel`).\n\n\u003e Lawful. First-class laws for type classes, which are enforced by the compiler.\n\nAs mentioned above, we meet laws/properties halfway with compile-time property tests.\n\n\u003e Hierarchical. A compiler-verified requirement that a subclass of a type class must have at least one more law than that type class.\n\n`TypeClass` requires at least one property per class. You can build type class hierarchies with [`extend/2`](TypeClass.Dependency.html#extend/2).\n\n\u003e Globally Unambiguous. Type class resolution that produces an error if there exists more than one instances which satisfies the constraints at the point where the compiler must choose an instance.\n\nElixir is dynamically typed, and so we cannot constrain functions at compile time. However, the point is well taken: rather than creating a renamed variant of a type so that you can have multiple instances (ex. [`Monoid`](https://hexdocs.pm/witchcraft/Witchcraft.Monoid.html#content) can be integer addition or multiplication), extend the TypeClass and give it the additional properties that you're interested in for each case (ex. `AdditiveMonoid` and `MultiplicativeMonoid` extend `Monoid`).\n\n\u003e Abstractable. The ability to abstract over type classes themselves.\n\nDe Goes is referring here to abstracting over typed holes. Elixir is dynamically typed, so this one doesn't apply to us.\n\n## Escape Hatches\n\nIn the cases that you _really need_ to override the prop checker, you have two options:\n\n### `@force_type_class true`\nThis will force the prop checker to pass for all data types for the class.\nThis is generally a bad idea (see section on principled classes above),\nbut may be nessesary for some extreme edge cases.\n\nUsing this option will trigger a compile time warning. See [`defclass/2`](TypeClass.html#defclass/2) on how to use.\n\n### `@force_type_instance true`\nThis will force the prop checker to pass for a particular instance.\n\nThis is sometimes needed, since TypeClass's property checker\nmay not be able to accurately validate all data types correctly for\nall possible cases, especially when only subsets of built-in types are valid.\n(For example, a class that can only be deifned on 2-tuples).\n\nForcing a type instance in this way is like telling\nthe checker \"trust me this is correct\", and should only be used as\na last resort. If at all possible, try to use [`custom_generator/2`](TypeClass.Property.Generator.Custom.html#custom_generator/2) first.\n\nUsing this option will trigger a compile time warning. See [`definst/3`](TypeClass.html#definst/3) on how to use.\n\n### [`custom_generator/2`](TypeClass.Property.Generator.Custom.html#custom_generator/2)\nIf you need to specify a certain type of data that conforms to the type class,\nyou can specify it with [`custom_generator/2`](TypeClass.Property.Generator.Custom.html#custom_generator/2) inside of the [`definst/3`](TypeClass.html#definst/3).\n\nFor example, Tuples should only have instances for 2-tuples for certain classes,\nso we can restrict the prop test data to 2-tuples rather than n-tuples.\n\nThe generator must conform to the standard unary generator format.\n\n```elixir\ndefinst AwesomeClass, for: Tuple do\n  custom_generator(a) do\n    {:always_two, a}\n  end\n\n  # the rest as normal\n  def awesome_level(_), do: 9000\nend\n```\n\n## Example\n\n### TypeClass\n\n```elixir\ndefclass Algebra.Monoid do\n  extend Algebra.Semigroup\n\n  where do\n    def empty(sample)\n  end\n\n  properties do\n    def left_identity(data) do\n      a = generate(data)\n      Semigroup.concat(Monoid.empty(a), a) == a\n    end\n\n    def right_identity(data) do\n      a = generate(data)\n      Semigroup.concat(a, Monoid.empty(a)) == a\n    end\n  end\nend\n```\n\n### Haskell\n\nThe _rough_ equivalent in Haskell\n\n```haskell\nmodule Algebra.Monoid where\n\nclass (Setoid a, Semigroup a) =\u003e Monoid a where\n  identity :: a -\u003e a\n\n  -- Not actually needed in this case\n  -- Just here to illustrate including functions for minimal definitions\n  append_id :: a -\u003e a\n  append_id a = identity a `append` a\n\ninstance Monoid [a] where\n  identity _ = []\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwitchcrafters%2Ftype_class","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwitchcrafters%2Ftype_class","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwitchcrafters%2Ftype_class/lists"}