{"id":18361690,"url":"https://github.com/paldepind/union-type","last_synced_at":"2025-04-05T17:06:47.105Z","repository":{"id":32050325,"uuid":"35621932","full_name":"paldepind/union-type","owner":"paldepind","description":"A small JavaScript library for defining and using union types.","archived":false,"fork":false,"pushed_at":"2019-06-05T12:33:58.000Z","size":96,"stargazers_count":478,"open_issues_count":18,"forks_count":28,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-01-02T19:04:03.339Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/paldepind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-14T16:17:36.000Z","updated_at":"2024-11-14T03:08:02.000Z","dependencies_parsed_at":"2022-09-02T01:11:34.218Z","dependency_job_id":null,"html_url":"https://github.com/paldepind/union-type","commit_stats":null,"previous_names":["paldepind/union-type-js"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paldepind%2Funion-type","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paldepind%2Funion-type/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paldepind%2Funion-type/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paldepind%2Funion-type/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paldepind","download_url":"https://codeload.github.com/paldepind/union-type/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369952,"owners_count":20927928,"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":[],"created_at":"2024-11-05T22:35:08.503Z","updated_at":"2025-04-05T17:06:47.083Z","avatar_url":"https://github.com/paldepind.png","language":"JavaScript","funding_links":[],"categories":["Javascript","Libraries"],"sub_categories":["[Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)","Algebraic Data Types"],"readme":"# union-type\n\nA small JavaScript library for defining and using union types.\n\nUnion types are a way to group different values together. You can think of them\nas a powerful form of enums with the possibility to have additional data\nassociated with the possible values.\n\n## Table of contents\n\n* [Tutorial](#tutorial)\n  * [Defining a union type](#defining-a-union-type)\n  * [Constructing a union type](#constructing-a-union-type)\n  * [Switching on union types](#switching-on-union-types)\n  * [Extracting fields from a union type](#extracting-fields-from-a-union-type)\n  * [Recursive union types](#recursive-union-types)\n* [Author \u0026 license](#author--license)\n\n## Tutorial\n\n### Defining a union type\n\nunion-type exports a single function `Type`. Union types are created by\npassing the `Type` function a definition object. The easiest way to define\na Type is as follows:\n\n```javascript\nfunction isNumber(n) { return typeof n === 'number'; }\nvar Point = Type({Point: [isNumber, isNumber]});\n```\n\nThe keys of the object are the names of the values that the type can have. The\nvalues of the object are arrays describing the fields of the value. The fields\ncan be described by a _validator function_. When a value of the type is\nconstructed the values passed to the constructor will have to pass the\nvalidator predicate.\n\nAlternatively the fields can be specified by one of the standard built-in\nconstructors `Number`, `String`, `Object`, `Array` or `Function`. union-type\nwill detect these constructors and convert them to matching validator functions.\nThus the above example is equivalent to this:\n\n```javascript\nvar Point = Type({Point: [Number, Number]});\n```\n\n### Records\n\nInstead of supplying only the types of the individual constructors it is also\npossible to define records using object descriptions:\n\n```javascript\nvar Point = Type({Point: {x: Number, y: Number}});\n```\n\n### Instance methods\n\nFurthermore it is possible to add instance methods. A [Maybe type](https://en.wikipedia.org/wiki/Option_type) with a map\nfunction could thus be defined as follows:\n\n```javascript\nvar add = first =\u003e second =\u003e first + second;  \n\nvar T = function () { return true; };\nvar Maybe = Type({Just: [T], Nothing: []});\nMaybe.prototype.map = function(fn) {\n  return Maybe.case({\n    Nothing: () =\u003e Maybe.Nothing,\n    Just: (v) =\u003e Maybe.Just(fn(v))\n  }, this);\n};\nvar just = Maybe.Just(1);\nvar nothing = Maybe.Nothing;\nnothing.map(add(1)); // =\u003e Nothing\njust.map(add(1)); // =\u003e Just(2)\n```\n\nFinally fields can be described in terms of other types.\n\n```javascript\nvar Shape = Type({\n  Circle: [Number, Point],\n  Rectangle: [Point, Point]\n});\n```\n\nThe values of a type can also have no fields at all.\n\n```javascript\nvar NotifySetting = Type({Mute: [], Vibrate: [], Sound: [Number]});\n```\n\n### Constructing a union type\n\nThe `Type` function returns an object with constructor function for the\ndifferent specified values. Thus, once you've defined a union type like this\n\n```javascript\nvar Point = Type({Point: [Number, Number]});\nvar Shape = Type({\n  Circle: [Number, Point],\n  Rectangle: [Point, Point]\n});\n```\n\nYou can create values like this:\n\n```javascript\nvar center = Point.Point(12, 7);\nvar radius = 8;\nvar circle = Shape.Circle(radius, center);\n```\n\nIf you in any way pass a field value that does not match the specification a\nhelpful error is thrown.\n\n```javascript\nvar p = Point.Point('foo', 4);\n// throws TypeError: bad value 'foo' passed to first argument of constructor Point\n```\n\nAs mentioned earlier you can also define records using object descriptions:\n\n```javascript\nvar Point = Type({Point: {x: Number, y: Number}});\n```\n\nTypes defined using the record syntax have to be constructed using the respective\n`\u003cname\u003eOf` constructor. The Point type above is hence constructed using `PointOf`:\n\n```javascript\nvar p = Point.PointOf({x: 1, y: 1});\n```\n\nAlternatively records can be constructed in the same way as regular types.\n\n```javascript\nvar p = Point.Point(1, 1);\n```\n\n### Switching on union types\n\nEvery created type has a `case` function available along with its value\nconstructors. `case` can be used as a control structure for handling the\ndifferent values a type can have:\n\n```javascript\nvar Action = Type({Up: [], Right: [], Down: [], Left: [], Jump: [], Fire: [Number]});\n\nvar player = {x: 0, y: 0};\n\nvar advancePlayer = function(action, player) {\n  return Action.case({\n    Up: function() { return {x: player.x, y: player.y - 1}; },\n    Right: function() { return {x: player.x + 1, y: player.y}; },\n    Down: function() { return {x: player.x, y: player.y + 1}; },\n    Left: function() { return {x: player.x - 1, y: player.y}; },\n    _: function() { return player; }\n  }, action);\n};\n```\n\nOr with ECMAScript 6 syntax.\n\n```javascript\nconst advancePlayer = (action, player) =\u003e\n  Action.case({\n    Up: () =\u003e ({x: player.x, y: player.y - 1}),\n    Right: () =\u003e ({x: player.x + 1, y: player.y}),\n    Down: () =\u003e ({x: player.x, y: player.y + 1}),\n    Left: () =\u003e ({x: player.x - 1, y: player.y}),\n    _: () =\u003e player,\n  }, action);\n```\n\n`case` will extract the fields of a value and pass them in order to the\nrelevant function. A function to calculate the area of a shape could, for\ninstance, look like this.\n\n```javascript\nvar Shape = Type({Circle: [Number, Point],\n                  Rectangle: [Point, Point]});\nvar area = (shape) =\u003e\n  Shape.case({\n    Circle: (radius, _) =\u003e Math.PI * radius * radius,\n    Rectangle: (p1, p2) =\u003e (p2[0] - p1[0]) * (p2[1] - p1[1])\n  }, shape);\n```\n\n`case` is curried so we could have created the above function simply by\nnot passing the second parameter to `case`.\n\n```javascript\nvar area = Shape.case({\n  Circle: (radius, _) =\u003e Math.PI * radius * radius,\n  Rectangle: (p1, p2) =\u003e (p2[0] - p1[0]) * (p2[1] - p1[1])\n});\n```\n\n`caseOn` is similar to `case`, but allows passing additional data directly\ninto each case function. With `caseOn`, the `advancePlayer` example from\nbefore could be written in \"point-free style\" like this:\n\n```javascript\n// No need to wrap this into a function that passes `player`\nconst advancePlayer = Action.caseOn({\n  Up: (player) =\u003e ({x: player.x, y: player.y - 1}),\n  Right: (player) =\u003e ({x: player.x + 1, y: player.y}),\n  Down: (player) =\u003e ({x: player.x, y: player.y + 1}),\n  Left: (player) =\u003e ({x: player.x - 1, y: player.y}),\n  _: (player) =\u003e player\n});\n\nadvancePlayer(Action.Up, player);\n```\n\nAs a catch all you can supply a property with the key `_` to case. When a type\ndoesn't match another handler `_` will be used. The fields will NOT be extracted\nwhen matching on `_` as this may result in inconsistent argument positions.\n\n\n```javascript\nconst advancePlayerOnlyUp = (action, player) =\u003e\n  Action.case({\n    Up: () =\u003e ({x: player.x, y: player.y - 1}),\n    _: () =\u003e player,\n  });\n```\n\nIn addition to the static `case` and `caseOn` functions on a type, instances of\na type have `case` and `caseOf` methods, so for example\n\n```javascript\nAction.case({\n  Up: () =\u003e ({x: player.x, y: player.y - 1}),\n  Right: () =\u003e ({x: player.x + 1, y: player.y}),\n  Down: () =\u003e ({x: player.x, y: player.y + 1}),\n  Left: () =\u003e ({x: player.x - 1, y: player.y}),\n  _: () =\u003e player,\n}, action);\n```\n\ncould equivalently be written as\n\n```javascript\naction.case({\n  Up: () =\u003e ({x: player.x, y: player.y - 1}),\n  Right: () =\u003e ({x: player.x + 1, y: player.y}),\n  Down: () =\u003e ({x: player.x, y: player.y + 1}),\n  Left: () =\u003e ({x: player.x - 1, y: player.y}),\n  _: () =\u003e player,\n});\n```\n\n### Extracting fields from a union type\n\nIf your type was defined using the record syntax you can access the fields\nthrough the name you specified:\n\n```javascript\nvar Person = Type({Person: {name: String, age: Number, shape: Shape}});\nvar person = Person.PersonOf({name: 'Simon', age: 21, shape: Circle});\nvar name = person.name;\nvar age = person.age;\nvar favoriteShape = person.shape;\n```\n\nIf your type was not created using the record syntax the fields have to\nbe extracted by indexing your union type:\n\n```javascript\nvar Person = Type({Person: [String, Number, Shape]});\nvar person = Person.Person('Simon', 21, Circle);\nvar name = person[0];\nvar age = person[1];\nvar favoriteShape = person[2];\n```\n\nUsing the destructuring assignment in ECMAScript 6 it is possible to\nconcisely extract all fields of a type.\n\n```javascript\nvar [name, age, favoriteShape] = person;\n```\n\n### Recursive union types\n\nIt is possible to define recursive union types. In the example below, `List` is\nbeing used in it's own definition, thus it is still `undefined` when being\npassed to `Type`. Therefore `Type` interprets `undefined` as being a recursive\ninvocation of the type currently being defined.\n\n```javascript\nvar List = Type({Nil: [], Cons: [R.T, List]});\n```\n\nWe can write a function that recursively prints the content of our cons list.\n\n```javascript\nvar toString = List.case({\n  Cons: (head, tail) =\u003e head + ' : ' + toString(tail),\n  Nil: () =\u003e 'Nil',\n});\n\nvar list = List.Cons(1, List.Cons(2, List.Cons(3, List.Nil)));\nconsole.log(toString(list)); // =\u003e '1 : 2 : 3 : Nil'\n```\n\n### Disabling type checking\n\nType checking can be disabled, for instance in production, by setting\n`Type.check` to `false`.\n\n## Author \u0026 license\n\nunion-type was made by [paldepind](https://twitter.com/paldepind) and is\nreleased under the MIT license. I hope you find it useful.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaldepind%2Funion-type","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaldepind%2Funion-type","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaldepind%2Funion-type/lists"}