{"action":"created","comment":{"url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/comments/172372981","pull_request_review_id":101378561,"id":172372981,"diff_hunk":"@@ -0,0 +1,112 @@\n+// @flow\n+\n+import deepEqual from \"lodash.isequal\";\n+\n+export type Address = {|\n+  +repositoryName: string,\n+  +pluginName: string,\n+  +id: string,\n+|};\n+\n+export interface Addressable {\n+  +address: Address;\n+}\n+\n+export type SansAddress<T: Addressable> = $Exact<$Diff<T, {+address: Address}>>;\n+\n+export type AddressMapJSON<T: Addressable> = {\n+  [serializedAddress: string]: SansAddress<T>,\n+};\n+\n+/**\n+ * A data structure for storing addressable objects, keyed by their\n+ * addresses.\n+ */\n+export class AddressMap<T: Addressable> {\n+  // TODO(@wchargin): Evaluate performance gains from using a triple-map\n+  // here. Cf. https://jsperf.com/address-string-302039074.\n+  _data: {[serializedAddress: string]: T};\n+\n+  /**\n+   * Create an empty `AddressMap`.\n+   */\n+  constructor() {\n+    this._data = {};\n+  }\n+\n+  /**\n+   * Test whether this map logically equals another map. Two maps are\n+   * logically equal if they contain the same keys and the values at\n+   * each key are deep-equal.\n+   */\n+  equals(that: AddressMap<T>): boolean {\n+    return deepEqual(this._data, that._data);\n+  }\n+\n+  toJSON(): AddressMapJSON<T> {\n+    const result = {};\n+    Object.keys(this._data).forEach((key) => {\n+      const node = {...this._data[key]};\n+      delete node.address;\n+      result[key] = node;\n+    });\n+    return result;\n+  }\n+\n+  static fromJSON(json: AddressMapJSON<T>): AddressMap<T> {\n+    const result: AddressMap<T> = new AddressMap();\n+    Object.keys(json).forEach((key) => {\n+      result._data[key] = {...json[key], address: JSON.parse(key)};\n+    });\n+    return result;\n+  }\n+\n+  /**\n+   * Add the given object to the map, replacing any existing value for\n+   * the same address.\n+   *\n+   * Returns `this` for easy chaining.\n+   */\n+  add(t: T): this {\n+    if (t.address == null) {\n+      throw new Error(`address is ${String(t.address)}`);\n+    }\n+    const key = JSON.stringify(t.address);\n+    this._data[key] = t;\n+    return this;\n+  }\n+\n+  /**\n+   * Get the object at the given address, if it exists, or `undefined`\n+   * otherwise.\n+   */\n+  get(address: Address): T {\n+    if (address == null) {\n+      throw new Error(`address is ${String(address)}`);\n+    }\n+    const key = JSON.stringify(address);\n+    return this._data[key];\n+  }\n+\n+  /**\n+   * Get all objects stored in the map, in some unspecified order.\n+   */\n+  getAll(): T[] {\n+    return Object.keys(this._data).map((k) => this._data[k]);\n+  }\n+}\n+\n+/**\n+ * Create a copy of the given array and sort its elements by their\n+ * addresses. The original array and its elements are not modified.\n+ */\n+export function sortedByAddress<T: Addressable>(xs: T[]) {","path":"src/backend/address.js","position":103,"original_position":103,"commit_id":"bd943e69a430178e45964d56d28aa208d6e4e0a2","original_commit_id":"bd943e69a430178e45964d56d28aa208d6e4e0a2","user":{"login":"wchargin","id":4317806,"avatar_url":"https://avatars3.githubusercontent.com/u/4317806?v=4","gravatar_id":"","url":"https://api.github.com/users/wchargin","html_url":"https://github.com/wchargin","followers_url":"https://api.github.com/users/wchargin/followers","following_url":"https://api.github.com/users/wchargin/following{/other_user}","gists_url":"https://api.github.com/users/wchargin/gists{/gist_id}","starred_url":"https://api.github.com/users/wchargin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wchargin/subscriptions","organizations_url":"https://api.github.com/users/wchargin/orgs","repos_url":"https://api.github.com/users/wchargin/repos","events_url":"https://api.github.com/users/wchargin/events{/privacy}","received_events_url":"https://api.github.com/users/wchargin/received_events","type":"User","site_admin":false},"body":"It is for tests, yes, but I also think that this is a reasonable place for it. It defines how to sort addresses, and lives in `address.js`. (Compare: in Java/etc. you'd have `implements Comparable<Address>` on the class; in Haskell you'd have `instance Ord` in the module.)","created_at":"2018-03-06T00:15:26Z","updated_at":"2018-03-06T00:15:26Z","html_url":"https://github.com/sourcecred/sourcecred/pull/67#discussion_r172372981","pull_request_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/comments/172372981"},"html":{"href":"https://github.com/sourcecred/sourcecred/pull/67#discussion_r172372981"},"pull_request":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67"}},"in_reply_to_id":172368605},"pull_request":{"url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67","id":173017496,"html_url":"https://github.com/sourcecred/sourcecred/pull/67","diff_url":"https://github.com/sourcecred/sourcecred/pull/67.diff","patch_url":"https://github.com/sourcecred/sourcecred/pull/67.patch","issue_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/67","number":67,"state":"open","locked":false,"title":"Create an `AddressMap` abstraction","user":{"login":"wchargin","id":4317806,"avatar_url":"https://avatars3.githubusercontent.com/u/4317806?v=4","gravatar_id":"","url":"https://api.github.com/users/wchargin","html_url":"https://github.com/wchargin","followers_url":"https://api.github.com/users/wchargin/followers","following_url":"https://api.github.com/users/wchargin/following{/other_user}","gists_url":"https://api.github.com/users/wchargin/gists{/gist_id}","starred_url":"https://api.github.com/users/wchargin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wchargin/subscriptions","organizations_url":"https://api.github.com/users/wchargin/orgs","repos_url":"https://api.github.com/users/wchargin/repos","events_url":"https://api.github.com/users/wchargin/events{/privacy}","received_events_url":"https://api.github.com/users/wchargin/received_events","type":"User","site_admin":false},"body":"Summary:\r\nThis commit reifies the concept of an `Addressable`, which is any object\r\nthat has a covariant `address: Address` attribute, and implements a\r\nsimple data structure for storing addressable items keyed against their\r\naddresses. Instances of `AddressMap` can replace the four fields of\r\n`Graph`:\r\n```js\r\n_nodes: AddressMap<Node<mixed>>;\r\n_edges: AddressMap<Edge<mixed>>;\r\n_outEdges: AddressMap<{|+address: Address, +edges: Address[]|}>;\r\n_inEdges: AddressMap<{|+address: Address, +edges: Address[]|}>;\r\n```\r\n\r\nSee discussion at #65.\r\n\r\nTest Plan:\r\nNew unit tests included, with 100% coverage: `yarn flow && yarn test`.\r\n\r\nwchargin-branch: address-map","created_at":"2018-03-05T23:31:16Z","updated_at":"2018-03-06T00:15:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3a08e87fed7789efe2dc48b554f18c3b6afe1079","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67/commits","review_comments_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67/comments","review_comment_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/67/comments","statuses_url":"https://api.github.com/repos/sourcecred/sourcecred/statuses/bd943e69a430178e45964d56d28aa208d6e4e0a2","head":{"label":"sourcecred:wchargin-address-map","ref":"wchargin-address-map","sha":"bd943e69a430178e45964d56d28aa208d6e4e0a2","user":{"login":"sourcecred","id":35711667,"avatar_url":"https://avatars1.githubusercontent.com/u/35711667?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcecred","html_url":"https://github.com/sourcecred","followers_url":"https://api.github.com/users/sourcecred/followers","following_url":"https://api.github.com/users/sourcecred/following{/other_user}","gists_url":"https://api.github.com/users/sourcecred/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcecred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcecred/subscriptions","organizations_url":"https://api.github.com/users/sourcecred/orgs","repos_url":"https://api.github.com/users/sourcecred/repos","events_url":"https://api.github.com/users/sourcecred/events{/privacy}","received_events_url":"https://api.github.com/users/sourcecred/received_events","type":"Organization","site_admin":false},"repo":{"id":120145570,"name":"sourcecred","full_name":"sourcecred/sourcecred","owner":{"login":"sourcecred","id":35711667,"avatar_url":"https://avatars1.githubusercontent.com/u/35711667?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcecred","html_url":"https://github.com/sourcecred","followers_url":"https://api.github.com/users/sourcecred/followers","following_url":"https://api.github.com/users/sourcecred/following{/other_user}","gists_url":"https://api.github.com/users/sourcecred/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcecred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcecred/subscriptions","organizations_url":"https://api.github.com/users/sourcecred/orgs","repos_url":"https://api.github.com/users/sourcecred/repos","events_url":"https://api.github.com/users/sourcecred/events{/privacy}","received_events_url":"https://api.github.com/users/sourcecred/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sourcecred/sourcecred","description":"credit attribution for open-source projects. like street cred, for source code.","fork":false,"url":"https://api.github.com/repos/sourcecred/sourcecred","forks_url":"https://api.github.com/repos/sourcecred/sourcecred/forks","keys_url":"https://api.github.com/repos/sourcecred/sourcecred/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sourcecred/sourcecred/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sourcecred/sourcecred/teams","hooks_url":"https://api.github.com/repos/sourcecred/sourcecred/hooks","issue_events_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/events{/number}","events_url":"https://api.github.com/repos/sourcecred/sourcecred/events","assignees_url":"https://api.github.com/repos/sourcecred/sourcecred/assignees{/user}","branches_url":"https://api.github.com/repos/sourcecred/sourcecred/branches{/branch}","tags_url":"https://api.github.com/repos/sourcecred/sourcecred/tags","blobs_url":"https://api.github.com/repos/sourcecred/sourcecred/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sourcecred/sourcecred/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sourcecred/sourcecred/git/refs{/sha}","trees_url":"https://api.github.com/repos/sourcecred/sourcecred/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sourcecred/sourcecred/statuses/{sha}","languages_url":"https://api.github.com/repos/sourcecred/sourcecred/languages","stargazers_url":"https://api.github.com/repos/sourcecred/sourcecred/stargazers","contributors_url":"https://api.github.com/repos/sourcecred/sourcecred/contributors","subscribers_url":"https://api.github.com/repos/sourcecred/sourcecred/subscribers","subscription_url":"https://api.github.com/repos/sourcecred/sourcecred/subscription","commits_url":"https://api.github.com/repos/sourcecred/sourcecred/commits{/sha}","git_commits_url":"https://api.github.com/repos/sourcecred/sourcecred/git/commits{/sha}","comments_url":"https://api.github.com/repos/sourcecred/sourcecred/comments{/number}","issue_comment_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/comments{/number}","contents_url":"https://api.github.com/repos/sourcecred/sourcecred/contents/{+path}","compare_url":"https://api.github.com/repos/sourcecred/sourcecred/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sourcecred/sourcecred/merges","archive_url":"https://api.github.com/repos/sourcecred/sourcecred/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sourcecred/sourcecred/downloads","issues_url":"https://api.github.com/repos/sourcecred/sourcecred/issues{/number}","pulls_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls{/number}","milestones_url":"https://api.github.com/repos/sourcecred/sourcecred/milestones{/number}","notifications_url":"https://api.github.com/repos/sourcecred/sourcecred/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sourcecred/sourcecred/labels{/name}","releases_url":"https://api.github.com/repos/sourcecred/sourcecred/releases{/id}","deployments_url":"https://api.github.com/repos/sourcecred/sourcecred/deployments","created_at":"2018-02-04T01:32:53Z","updated_at":"2018-03-01T16:05:07Z","pushed_at":"2018-03-05T23:32:17Z","git_url":"git://github.com/sourcecred/sourcecred.git","ssh_url":"git@github.com:sourcecred/sourcecred.git","clone_url":"https://github.com/sourcecred/sourcecred.git","svn_url":"https://github.com/sourcecred/sourcecred","homepage":"","size":432,"stargazers_count":13,"watchers_count":13,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":15,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":2,"open_issues":15,"watchers":13,"default_branch":"master"}},"base":{"label":"sourcecred:master","ref":"master","sha":"a798f9bac23014cd3a9c78a19685dbef86f977d4","user":{"login":"sourcecred","id":35711667,"avatar_url":"https://avatars1.githubusercontent.com/u/35711667?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcecred","html_url":"https://github.com/sourcecred","followers_url":"https://api.github.com/users/sourcecred/followers","following_url":"https://api.github.com/users/sourcecred/following{/other_user}","gists_url":"https://api.github.com/users/sourcecred/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcecred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcecred/subscriptions","organizations_url":"https://api.github.com/users/sourcecred/orgs","repos_url":"https://api.github.com/users/sourcecred/repos","events_url":"https://api.github.com/users/sourcecred/events{/privacy}","received_events_url":"https://api.github.com/users/sourcecred/received_events","type":"Organization","site_admin":false},"repo":{"id":120145570,"name":"sourcecred","full_name":"sourcecred/sourcecred","owner":{"login":"sourcecred","id":35711667,"avatar_url":"https://avatars1.githubusercontent.com/u/35711667?v=4","gravatar_id":"","url":"https://api.github.com/users/sourcecred","html_url":"https://github.com/sourcecred","followers_url":"https://api.github.com/users/sourcecred/followers","following_url":"https://api.github.com/users/sourcecred/following{/other_user}","gists_url":"https://api.github.com/users/sourcecred/gists{/gist_id}","starred_url":"https://api.github.com/users/sourcecred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sourcecred/subscriptions","organizations_url":"https://api.github.com/users/sourcecred/orgs","repos_url":"https://api.github.com/users/sourcecred/repos","events_url":"https://api.github.com/users/sourcecred/events{/privacy}","received_events_url":"https://api.github.com/users/sourcecred/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sourcecred/sourcecred","description":"credit attribution for open-source projects. like street cred, for source code.","fork":false,"url":"https://api.github.com/repos/sourcecred/sourcecred","forks_url":"https://api.github.com/repos/sourcecred/sourcecred/forks","keys_url":"https://api.github.com/repos/sourcecred/sourcecred/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sourcecred/sourcecred/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sourcecred/sourcecred/teams","hooks_url":"https://api.github.com/repos/sourcecred/sourcecred/hooks","issue_events_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/events{/number}","events_url":"https://api.github.com/repos/sourcecred/sourcecred/events","assignees_url":"https://api.github.com/repos/sourcecred/sourcecred/assignees{/user}","branches_url":"https://api.github.com/repos/sourcecred/sourcecred/branches{/branch}","tags_url":"https://api.github.com/repos/sourcecred/sourcecred/tags","blobs_url":"https://api.github.com/repos/sourcecred/sourcecred/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sourcecred/sourcecred/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sourcecred/sourcecred/git/refs{/sha}","trees_url":"https://api.github.com/repos/sourcecred/sourcecred/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sourcecred/sourcecred/statuses/{sha}","languages_url":"https://api.github.com/repos/sourcecred/sourcecred/languages","stargazers_url":"https://api.github.com/repos/sourcecred/sourcecred/stargazers","contributors_url":"https://api.github.com/repos/sourcecred/sourcecred/contributors","subscribers_url":"https://api.github.com/repos/sourcecred/sourcecred/subscribers","subscription_url":"https://api.github.com/repos/sourcecred/sourcecred/subscription","commits_url":"https://api.github.com/repos/sourcecred/sourcecred/commits{/sha}","git_commits_url":"https://api.github.com/repos/sourcecred/sourcecred/git/commits{/sha}","comments_url":"https://api.github.com/repos/sourcecred/sourcecred/comments{/number}","issue_comment_url":"https://api.github.com/repos/sourcecred/sourcecred/issues/comments{/number}","contents_url":"https://api.github.com/repos/sourcecred/sourcecred/contents/{+path}","compare_url":"https://api.github.com/repos/sourcecred/sourcecred/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sourcecred/sourcecred/merges","archive_url":"https://api.github.com/repos/sourcecred/sourcecred/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sourcecred/sourcecred/downloads","issues_url":"https://api.github.com/repos/sourcecred/sourcecred/issues{/number}","pulls_url":"https://api.github.com/repos/sourcecred/sourcecred/pulls{/number}","milestones_url":"https://api.github.com/repos/sourcecred/sourcecred/milestones{/number}","notifications_url":"https://api.github.com/repos/sourcecred/sourcecred/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sourcecred/sourcecred/labels{/name}","releases_url":"https://api.github.com/repos/sourcecred/sourcecred/releases{/id}","deployments_url":"https://api.github.com/repos/sourcecred/sourcecred/deployments","created_at":"2018-02-04T01:32:53Z","updated_at":"2018-03-01T16:05:07Z","pushed_at":"2018-03-05T23:32:17Z","git_url":"git://github.com/sourcecred/sourcecred.git","ssh_url":"git@github.com:sourcecred/sourcecred.git","clone_url":"https://github.com/sourcecred/sourcecred.git","svn_url":"https://github.com/sourcecred/sourcecred","homepage":"","size":432,"stargazers_count":13,"watchers_count":13,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":15,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":2,"open_issues":15,"watchers":13,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67"},"html":{"href":"https://github.com/sourcecred/sourcecred/pull/67"},"issue":{"href":"https://api.github.com/repos/sourcecred/sourcecred/issues/67"},"comments":{"href":"https://api.github.com/repos/sourcecred/sourcecred/issues/67/comments"},"review_comments":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67/comments"},"review_comment":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sourcecred/sourcecred/pulls/67/commits"},"statuses":{"href":"https://api.github.com/repos/sourcecred/sourcecred/statuses/bd943e69a430178e45964d56d28aa208d6e4e0a2"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"wchargin"}}