{"id":17069609,"url":"https://github.com/freethenation/unify.js","last_synced_at":"2025-04-12T19:22:16.903Z","repository":{"id":5857668,"uuid":"7074519","full_name":"freethenation/unify.js","owner":"freethenation","description":"An Efficient JavaScript Unification Library","archived":false,"fork":false,"pushed_at":"2015-02-10T01:24:10.000Z","size":350,"stargazers_count":19,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-11T19:54:57.693Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/freethenation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-12-09T02:02:33.000Z","updated_at":"2020-04-14T03:12:12.000Z","dependencies_parsed_at":"2022-09-02T00:42:18.904Z","dependency_job_id":null,"html_url":"https://github.com/freethenation/unify.js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freethenation%2Funify.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freethenation%2Funify.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freethenation%2Funify.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/freethenation%2Funify.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/freethenation","download_url":"https://codeload.github.com/freethenation/unify.js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248618834,"owners_count":21134305,"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-10-14T11:27:22.222Z","updated_at":"2025-04-12T19:22:16.864Z","avatar_url":"https://github.com/freethenation.png","language":"CoffeeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/freethenation/unify.js.png?branch=master)](https://travis-ci.org/freethenation/unify.js)\n# Unify.js\n_________________________\nUnify.js is an efficient javascript unification library that operates in linear time. It can be installed via\nnpm using the command `npm install unify`.\n \n# What is Unification?\n_________________________\nUnification is an algorithmic process that attempts to make two data structures identical by substituting/binding portions of them to each other. It is probably easiest to understand what unification is by looking at an example.\n\n    var rectangle1 = {\n        location:[25, 35],\n        size:[100, variable(\"height\")],\n        color:\"#000000\"\n    };\n    var rectangle2 = {\n        location:variable(\"location\"),\n        size:[100, 100],\n        color:\"#000000\"\n    };\n    //Unify the rectangles\n    out.get(\"height\") == 100\n    out.get(\"location\") == [25,35]\n\nIn the above example if the two rectangle structures were unified. Each of them have a variable which is substituted/bound to a value in the other. The variable \"height\" in rectangle1 would be bound to the value \"100\" from rectangle2. The variable \"location\" in rectangle2 is bound to the value \"[25, 35]\" from rectangle1.\n\n#Uses for Unification?\n________________________________\nUnification has lots of uses including. Examples of how each of these tasks can be accomplished with unify.js can be found below.\n\n* Extracting data\n* Validating data\n* Transforming data\n\n# Basic Usage\n________________________________\nBelow is a basic example of how use unify.js.\n\n```javascript\n//import unify.js\nvar unify = require('unify');\nvar variable = unify.variable;\n//create some data structures to be unified\nvar rectangle1 = {\n    location:[25, 35],\n    size:[100, variable(\"height\")],\n    color:\"#000000\"\n};\nvar rectangle2 = {\n    location:variable(\"location\"),\n    size:[100, 100],\n    color:\"#000000\"\n};\n//box the objects so they can be unified\nvar boxedRect1 = unify.box(rectangle1);\nvar boxedRect2 = unify.box(rectangle2);\n//preform the unification\nvar result = boxedRect1.unify(boxedRect2);\n//check if unification succeeded and print the results\nif(result) {\n  //print \"rectangle1 height: 100\" to the console\n  console.log(\"rectangle1  height: \" + \n    boxedRect1.get(\"height\").toString());\n  //print \"rectangle2 location: [25, 35]\" to the console\n  console.log(\"rectangle2 location: [\" + \n    boxedRect2.get(\"location\")[0] + \", \" +\n    boxedRect2.get(\"location\")[1]  + \"]\");\n}\nelse {\n  console.log('Unification Failed!');\n}\n```\n    \nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/fased/2/edit)\n\nIf you were unable to follow along don't worry the various aspects of the code above are explained in more detail below.\n\n# Variables\n________________________________\nVariables are placeholders that can be bound/replaced when unifying two data structures. In unify.js they are created by calling\n    \n    unify.variable(variableName, typeFunc=null);\n\nTwo variable with the same name will have the same value after unification. The value the variable is bound to may be looked up after unification using the variable's name.\n\n### Variable Types\n\nThe `typeFunc` parameter is optional and allows you to specify a function that will be used to validate the value bound to a variable. Right before a value is bound it will be passed to the validation function if the validation function returns true binding will succeed otherwise binding will fail.\n\nSeveral predefined type validation functions are provided in the `unify.types` namespace:\n\n* isUndef: returns true if the variable is undefined\n* isBool: returns true if the variable is a boolean\n* isArray: returns true if the variable is an array\n* isStr: returns true if the variable is a string\n* isNum: returns true if the variable is a number\n* isObj: returns true if the variable is an object\n* isValueType: returns true if the variable is a boolean, string, or number\n\n```javascript\nvar unify = require('unify');\nvar variable = unify.variable;\nvar isNum = function(o){return typeof(o) == \"number\";};\nvar expr1 = unify.box([variable(\"X\", isNum),1]);\nvar expr2 = unify.box([\"string\",1]);\nvar expr3 = unify.box([1,1]);\nif (expr1.unify(expr2)) {\n  console.log(\"Unification successful! X=\" + expr1.get(\"X\").toString());\n}\nelse {\n  console.log(\"Unification unsuccessful!\");\n}\nif (expr1.unify(expr3)) {\n  console.log(\"Unification successful! X=\" + expr1.get(\"X\").toString());\n}\nelse {\n  console.log(\"Unification unsuccessful!\");\n}\n//The following should be written to the console\n//Unification unsuccessful!\n//Unification successful! X=1\n```\n\nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/nipok/1/edit)\n\nIn the above example the first unification fails because `isNum` function returns false when the value `\"string\"` is passed. The second unification succeeds because `isNum` returns true when the value `1` is passed.\n\n### Wildcard Variables\n\nIf a variable's name is \"_\" then a wildcard variable is created which can take on any value, bind to any other variable, and whose value cannot be retrieved.\n\n### List Variables\n\nList variables allow you to unify to a list without knowing its exact length. List variables are created by making the variable name start with \"$\".\n\n```javascript\nvar unify = require('unify');\nvar variable = unify.variable;\nvar expr1 = unify.box([[3,4],2,3,4]);\nvar expr2 = unify.box([variable(\"a\"),2,variable(\"$a\")]);\nif (expr1.unify(expr2)) {\n  console.log(\"Unification successful! a=[\" + expr2.get(\"a\").join() + \"]\");\n}\nelse {\n  console.log(\"Unification unsuccessful!\");\n}\n//The following should be written to the console\n//Unification successful! a=[3,4]\n```\n\nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/ratus/1/edit)\n\nLooking at the example above the variable `a` is referenced twice, once as `\"a\"` and once as `\"$a\"`. The first reference, `\"a\"`, binds as normal to the first element in the array, `[3,4]`. The second reference, `\"$a\"` binds to the remaining elements in the list, `[3,4]`.\n\n# Boxing\n________________________________\nThe algorithm used by unify.js requires that objects be \"boxed\" before an object can be unified. Boxing consits of two steps:\n\n* Wrapping all value types in objects so they can be referenced.\n* Converting all objects to arrays and flagging them as objects so they can be reconstructed. Objects must be converted to arrays because unification is order dependent and the keys in javascript objects/dictionaries are unordered.\n\n# TreeTin\n________________________________\nCalling the box function on an object returns a TreeTin. A TreeTin provides a variety of methods related to unification and is the main interface through which you will work with unify.js. Some of the useful methods are:\n\n* `TreeTin.unify(tin)` : Unifies two tines together. Unify returns null when unification fails otherwise it returns `[tin1, tin2]`.\n* `TreeTin.get(varName, maxDepth=infinity)` : Gets a variable's bound value. If a variable is unbound or is bound to another variable a Variable object is returned. When a variable is retrieved it is unboxed. MaxDepth is an optional argument that allows you to speicify to what depth the variable is unboxed. The default value is usually what you want but sometimes you might want to specify a different value for performance reasons. \n* `TreeTin.getAll(maxDepth=infinity)` : Returns a dictionary containing all variables and their currently bound values.\n* `TreeTin.unbox(maxDepth=infinity)` : Reverts the box operation returning the original json with bound variable values substituted in.\n* `TreeTin.rollback()` : Reverts all variable bindings that have resulted from unifying this tin with other tins.\n* `TreeTin.bind(varName, expr)` : Allows you to manually bind a variable to an expression without unifying. If the variable is already bound null is returned otherwise `[tin, exprTin]` is returned. The expression can contain variables.\n\n# Algorithm and Performance\n________________________________\nThe algorithm used to preform unification for unify.js has a linear worst case complexity. The naive algorithm has an exponetal worst case complexity. If you want to learn more about the alogorithm [click here](http://www.jollybit.com/2012/04/efficient-linear-unification-algorithm.html).\n\n# More Examples\n________________________________\n### Validating data\n\n```javascript\nvar unify = require('unify');\nvar variable = unify.variable;\nvar validRectangle = unify.box({\n  topLeft:[0,0],\n  topRight:[1,0],\n  bottomLeft:[0,1],\n  bottomRight:[1,1]\n});\nvar invalidRectangle = unify.box({\n  topLeft:[0], //This is invalid there are not two coordinates!\n  topRight:[1,0],\n  bottomLeft:[0,1],\n  bottomRight:[1,1]\n});\nvar rectangleValidator = unify.box({\n  topLeft:[variable(\"_\",unify.isNum),variable(\"_\",unify.isNum)],\n  topRight:[variable(\"_\",unify.isNum),variable(\"_\",unify.isNum)],\n  bottomLeft:[variable(\"_\",unify.isNum),variable(\"_\",unify.isNum)],\n  bottomRight:[variable(\"_\",unify.isNum),variable(\"_\",unify.isNum)]\n});\n//Validate validRectangle\nif (rectangleValidator.unify(validRectangle)) {\n  console.log(\"validRectangle is valid!\");\n}\nelse {\n  console.log(\"validRectangle is invalid!\");\n}\n//We need to rollback the unification before we can validate agian\nrectangleValidator.rollback();\n//Validate invalidRectangle\nif (rectangleValidator.unify(invalidRectangle)) {\n  console.log(\"invalidRectangle is valid!\");\n}\nelse {\n  console.log(\"invalidRectangle is invalid!\");\n}\nrectangleValidator.rollback();\n//The above code will print the following to the console\n//validRectangle is valid!\n//invalidRectangle is invalid!\n```\n\nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/xiya/1/edit)\n\n### Extracting data\n\n```javascript\nvar unify = require('unify');\nvar arrayToString = function(arr){\n  return \"[\" + arr.join() + \"]\";\n};\nvar variable = unify.variable;\nvar rectangle = unify.box({\n  topLeft:[0,0],\n  topRight:[1,0],\n  bottomLeft:[0,1],\n  bottomRight:[1,1]\n});\nvar extractor = unify.box({\n  topLeft:variable(\"topLeft\",unify.isNum),\n  topRight:variable(\"topRight\",unify.isNum),\n  bottomLeft:variable(\"bottomLeft\",unify.isNum),\n  bottomRight:variable(\"bottomRight\",unify.isNum)\n});\n//extract the corners of the rectangle\nif (extractor.unify(rectangle)) {\n  console.log(\"topLeft: \" + arrayToString(extractor.get(\"topLeft\")));\n  console.log(\"topRight: \" + arrayToString(extractor.get(\"topRight\")));\n  console.log(\"bottomLeft: \" + arrayToString(extractor.get(\"bottomLeft\")));\n  console.log(\"bottomRight: \" + arrayToString(extractor.get(\"bottomRight\")));\n}\nelse {\n  console.log(\"Somthing went wrong. Unification failed!\");\n}\nextractor.rollback();\n//The above code will print the following to the console\n//\"topLeft: [0,0]\"\n//\"topRight: [1,0]\"\n//\"bottomLeft: [0,1]\"\n//\"bottomRight: [1,1]\"\n```\n\t\nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/jujim/1/edit)\n\n### Transforming data\n\n```javascript\n//This example transforms a rectangle and triangle into an array of lines\nvar unify = require('unify');\nvar arrayToString = function(arr){\n  var out = [];\n  for(var i=0; i \u003c arr.length; i++) {\n    var o = arr[i];\n    if((o !== null) \u0026\u0026 Array.isArray(o))\n      out.push(arrayToString(o));\n    else\n      out.push(o.toString());\n  }\n  return \"[\" + out.join() + \"]\";\n};\nvar variable = unify.variable;\nvar rectangle = {\n  topLeft:[0,0],\n  topRight:[1,0],\n  bottomLeft:[0,1],\n  bottomRight:[1,1]\n};\nvar transform  = unify.box({\n  topLeft:variable(\"topLeft\",unify.isNum),\n  topRight:variable(\"topRight\",unify.isNum),\n  bottomLeft:variable(\"bottomLeft\",unify.isNum),\n  bottomRight:variable(\"bottomRight\",unify.isNum),\n  lines:[\n    [variable(\"topLeft\"),variable(\"topRight\")],\n    [variable(\"topRight\"),variable(\"bottomRight\")],\n    [variable(\"bottomRight\"),variable(\"bottomLeft\")],\n    [variable(\"bottomLeft\"),variable(\"topLeft\")]\n  ]\n});\n/*\nneed to add the lines element to the rectangle\nso that it will unify but we don't care what it ends\nup being after unification.\n*/\nrectangle.lines = variable(\"_\");\nrectangle = unify.box(rectangle);\n//preform the actual unification\nif (transform.unify(rectangle)) {\n  //unbox transform so it is normal json object\n  var transformed = transform.unbox();\n  //loop through the lines and print them\n  for (var i=0; i\u003ctransformed.lines.length; i++) {\n    console.log(\"line \"+i.toString()+\": \"+\n      arrayToString(transformed.lines[i]));\n  }\n}\nelse {\n  console.log(\"Somthing went wrong. Unification failed!\");\n}\ntransform.rollback();\n//The above code will print the following to the console\n//\"line 0: [[0,0],[1,0]]\"\n//\"line 0: [[0,0],[1,0]]\"\n//\"line 2: [[1,1],[0,1]]\"\n//\"line 3: [[0,1],[0,0]]\"\n```\n    \nYou can play with this example at JS Bin by clicking [here](http://jsbin.com/vaho/1/edit)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreethenation%2Funify.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffreethenation%2Funify.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffreethenation%2Funify.js/lists"}