{"id":13508526,"url":"https://github.com/vic/expat","last_synced_at":"2025-04-06T13:10:42.296Z","repository":{"id":62429259,"uuid":"78658564","full_name":"vic/expat","owner":"vic","description":"Reusable, composable patterns across Elixir libraries","archived":false,"fork":false,"pushed_at":"2018-08-14T05:49:39.000Z","size":72,"stargazers_count":176,"open_issues_count":2,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T11:11:15.621Z","etag":null,"topics":["algebraic-data-types","composable-patterns","elixir","guards","macros","named-patterns","pattern-matching","patterns","union-types"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/expat","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-11T16:42:28.000Z","updated_at":"2025-01-23T23:56:19.000Z","dependencies_parsed_at":"2022-11-01T20:07:15.459Z","dependency_job_id":null,"html_url":"https://github.com/vic/expat","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fexpat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fexpat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fexpat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fexpat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vic","download_url":"https://codeload.github.com/vic/expat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247485287,"owners_count":20946398,"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":["algebraic-data-types","composable-patterns","elixir","guards","macros","named-patterns","pattern-matching","patterns","union-types"],"created_at":"2024-08-01T02:00:54.370Z","updated_at":"2025-04-06T13:10:42.279Z","avatar_url":"https://github.com/vic.png","language":"Elixir","funding_links":[],"categories":["Macros"],"sub_categories":[],"readme":"# Expat - Reusable, composable patterns in Elixir.\n\n[![Travis](https://img.shields.io/travis/vic/expat.svg)](https://travis-ci.org/vic/expat)\n[![Hex.pm](https://img.shields.io/hexpm/v/expat.svg?style=flat-square)](https://hexdocs.pm/expat)\n\n## About\n\nExpat is a library for creating composable pattern matchers.\n\nThat means, whenever you find yourself writing complex or long\npatterns in your functions, `expat` can be handy by allowing \nyou to split your pattern into re-usable and composable bits.\n\nThese named pattern matchers defined with `expat` can be used,\nfor example, to match over large phoenix parameters and keep\nyour action definitions short and concise. Since programmers\nread code all the time, their code should be optimized for\n*communicating their intent*, so instead of having your brain\nto parse all the way down the large structure pattern it\nwould be better to abstract that pattern with a name.\n\nAlso, as patterns get abstracted and split into re-usable\npieces they could be exported so other libraries (or your\nown umbrella applications) can communicate the rules for\nmatching data being passed between them.\n\nTo read more about the motivation and where this library comes from,\nyou can read [the v0 README](https://github.com/vic/expat/blob/v0/README.md)\n\n## `use Expat`\n\n### Named Patterns\n\nLet's start with some basic data examples. In Erlang/Elixir it's very\ncommon to use tagged tuples to communicate between functions.\nFor example, a function that can fail might return `{:error, reason}`\nor `{:ok, result}`. \n\nOf course these two element tuples are so small, that\nmost of the time it's better to use them as they *communicate the intent*\nthey are being used for. \n\nBut, using them can help us understand the basics of how `expat` works, \njust remember that `expat` takes patterns, and is not limited \nto some particular data structure.\n\n```elixir\n    defmodule MyPatterns do\n      use Expat\n\n      defpat ok({:ok, result})\n      defpat error({:error, reason})\n    end\n```\n\nSo, just like you'd be able to use `{:ok, result} = expr` to match\nsome expression, you can give the name `ok` to the `{:ok, result}` pattern.\n\nLater on, at some other module, you can use those named patterns.\n\n```elixir\n     iex\u003e import MyPatterns\n     iex\u003e Kernel.match?(ok(), {:ok, :hey})\n     true\n```\n\nIn the previous example, the `ok()` macro actually expanded to:\n\n\n```elixir\n     iex\u003e Kernel.match?({:ok, _}, {:ok, :hey})\n     true\n```\n\nNotice that even when the `ok` pattern definition says it\nhas an inner `result`, we didn't actually were interested in it,\nso `ok()` just ensures the data is matched with the structure\nmandated by its pattern and didn't bind any variable for us.\n\nIf we do need access to some of the pattern variables, we can bind\nthem by giving the pattern a `Keyword` of names to variables, \nfor example:\n\n```elixir\n     # One nice thing about expat is you can use your patterns\n     # anywhere you can currently write one, like in tests\n     iex\u003e assert error(reason: x) = {:error, \"does not exist\"}\n     iex\u003e x\n     \"does not exist\"\n```\n\nAnd of course, if you bind all the variables in a pattern, you can\nuse its macro as a data constructor, for example:\n\n```elixir\n     iex\u003e ok(result: \"done\")\n     {:ok, \"done\"}\n```\n\nThat's it for our tagged tuples example.\n\n### Combining patterns\n\nNow we know the basics of how to define and use named patterns,\nlet's see how we can combine them to form larger patterns.\n\nLet's use some structs instead of tuples, as that might be\na more common use case.\n\n```elixir\n     defmodule Pet do\n        defstruct [:name, :age, :owner, :kind]\n     end\n\n     defmodule Person do\n        defstruct [:name, :age, :country]\n     end\n\n     defmodule MyPatterns do\n       use Expat\n\n       defpat mexican(%Person{name: name, country: \"MX\"})\n\n       defpat mexican_parrot(%Pet{kind: :parrot, name: name,  age: age,\n                                     owner: mexican(name: owner_name)})\n     end\n\n     iex\u003e vic  = %Person{name: \"vic\", country: \"MX\"}\n     ...\u003e milo = %Pet{kind: :parrot, name: \"Milo\", owner: vic, age: 4}\n     ...\u003e\n     ...\u003e # here, we are only interested in the owner's name\n     ...\u003e mexican_parrot(owner_name: name) = milo\n     ...\u003e name\n     \"vic\"\n```\n\nAnd again, if you bind all the variables, it could be used as a data constructor\n\n```elixir\n     iex\u003e mexican_parrot(age: 1, name: \"Venus\", owner_name: \"Alicia\")\n     %Pet{kind: :parrot, name: \"Venus\", age: 1, owner: %Person{country: \"MX\", name: \"Alicia\", age: nil}}\n```\n\nThen you could use those patterns in a module of yours\n\n```elixir\n      defmodule Feed do\n         import MyPatterns\n\n         def with_mexican_food(bird = mexican_parrot(name: name, owner_name: owner)) do\n           \"#{name} is happy now!, thank you #{owner}\"\n         end\n      end\n```\n\nAnd the function head will actually match using the whole composite pattern, and only\nbind those fields you are interested in using.\n\n\n### Guarding patterns\n\nSince expat v1.0 it's now possible to use guards on your pattern definitions, and they\nwill be expanded at the call-site.\n\nFor example, let's build this year's flawed election system.\n\n```elixir\n      defmodule Voting.Patterns do\n        use Expat\n\n        defpat mexican(%Person{country: \"MX\"})\n\n        defpat adult(%{age: age}) when is_integer(age) and age \u003e= 18\n      end\n```\n\nNotice that the `adult` pattern matches anything with an integer age greater than 18 years\n(mexico's legal age to vote) by using `when` guards on the definition.\n\nNotice the `expat def can_vote?` part in the following code:\n\n```elixir\n       defmodule Voting do\n          use Expat\n          import Voting.Patterns\n          \n          def is_local?(mexican()), do: true\n          def is_local?(_), do: false\n          \n          expat def can_vote?(mexican() = adult()), do: true\n          def can_vote?(_), do: false\n       end\n```\n\n`expat` stands for `expand pattern` in the following expression, *and*\nexpand their guards in the correct place. \n\nSo our `can_vote?` function checks that the data given to it looks like\na mexican *and also* (since we are `=`ing two patterns), that the data\nrepresents an adult with legal age to vote by using guards.\n\n`expat` will work for `def`, `defmacro`, their private variants, `case`,\nand `fn`. \n\nActually you can give any expression into `expat`. And your patterns will\nbe expanded correctly within it. \n\nFor example, the previous module could be written like:\n\n```elixir\n          use Expat\n          import Voting.Patterns\n\n          expat defmodule Voting do\n\n            def is_local?(mexican()), do: true\n            def is_local?(_), do: false\n\n            def can_vote?(mexican() = adult()), do: true\n            def can_vote?(_), do: false\n          end\n          \n          # Un-import since its pattern macros\n          # were used only during compilation.\n          import Voting.Patterns, only: []\n```\n\n\n### Guarded data constructors\n\nAs mentioned previously, if you expand a pattern and bind all of it's inner\nvariables (provided the pattern was not defined with any `_` var), then you\nare effectively just building data from it.\n\nHowever, for patterns that include guards (or those expanding inner patterns\nincluding guards), an special bang function can be used to build data and make\nsure the guards are satisfied. \n\nBang constructors are positional, that means variables are bound in the order\nthey appear on your named pattern.\n\nFor example, for our previous `adult` pattern:\n\n```elixir\n    defpat adult(%{age: age}) when is_integer(age) and age \u003e= 18\n```\n\nThe `adult!(age)` constructor will be generated.\n\nSee [HOW_IT_WORKS](https://github.com/vic/expat/tree/master/HOW_IT_WORKS.md)\nfor more info on how guards are expanded within Expat.\n\n### Union Patterns\n\nThis is an Expat feature that lets you compose many named patterns into a single\n*union* pattern. They are explained best with code, see bellow.\n\nUsing unions, you can emulate things like\n[Algebraic data types](https://en.wikipedia.org/wiki/Algebraic_data_type)\n\nFor some examples, see:\n\n- [Natural numbers](https://github.com/vic/expat/tree/master/test/expat_nat_test.exs).\n- [Maybe](https://github.com/vic/expat/tree/master/test/expat_maybe_test.exs).\n- [Either](https://github.com/vic/expat/tree/master/test/expat_either_test.exs).\n- [Result](https://github.com/vic/expat/tree/master/test/expat_result_test.exs).\n- [Union on Struct](https://github.com/vic/expat/tree/master/test/expat_union_test.exs).\n\n\n### Documentation\n\nYour named pattern macros will be generated with documentation about what variables\nthey take and what they will expand to. If you are in IEx, be sure to checkout their\ndocumentation using something like: `h Voting.Patterns.adult`\n\nAlso, be sure to read the [documentation](https://hexdocs.pm/expat), and checkout some\nof the [tests](https://github.com/vic/expat/tree/master/test/).\n\nHappy Pattern Matching!\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:expat, \"~\u003e 1.0\"}\n  ]\nend\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fexpat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvic%2Fexpat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fexpat/lists"}