{"id":17267469,"url":"https://github.com/technius/tabler","last_synced_at":"2025-03-26T11:14:09.996Z","repository":{"id":143021386,"uuid":"127344570","full_name":"Technius/tabler","owner":"Technius","description":"An attempt at a user-defined data storage implemented with type-level programming","archived":false,"fork":false,"pushed_at":"2018-03-31T00:48:18.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-31T12:24:31.826Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","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/Technius.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-29T20:33:55.000Z","updated_at":"2018-03-31T00:48:19.000Z","dependencies_parsed_at":"2023-03-26T17:32:55.848Z","dependency_job_id":null,"html_url":"https://github.com/Technius/tabler","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/Technius%2Ftabler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Ftabler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Ftabler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Technius%2Ftabler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Technius","download_url":"https://codeload.github.com/Technius/tabler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245641437,"owners_count":20648644,"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-15T08:10:44.375Z","updated_at":"2025-03-26T11:14:09.977Z","avatar_url":"https://github.com/Technius.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tabler\n\nThis is an attempt at a data storage application in which the schema is\nuser-defined; the hard part is figuring out how to use type-level programming\ntechniques in Haskell to improve correctness of the data storage functions.\n\n## What is this about?\n\nIn most webapps, domain models are usually hardedcoded. For example,\nan e-commerce webapp might have a store item encoded as something similar to\n\n```haskell\ndata StoreItem =\n  MkStoreItem {\n    getId :: Int,\n    getName :: String,\n    getDesc :: String,\n    getPrice :: Double\n  }\n```\n\nand a shopping cart may be encoded as\n\n```haskell\ndata CartEntry =\n  MkCartEntry {\n    getId :: Int,\n    getQuatity :: Int\n  }\n\nnewtype Cart = MkCart [CartEntry]\n```\n\nGenerally, most of these data-driven webapps represent their domain models as\nrecord types. Businesses usually store such data in databases, (hopefully)\nmaking sure to ensure that only valid data is stored. Individuals managing data\nfor personal consumption (e.g. grocery lists, todo-lists, etc.) may prefer to\nuse spreadsheets instead. While spreadsheets are far simpler than databases,\nthey do tend to lose the data validation aspects, which could be problematic for\ndata entry. Additionally, databases have the upper hand in terms of searching\nand filtering capabilities.\n\nIt would be nice to have a middle ground: have a custom, user-defined schema and\nallows for validated data entry and enhanced search and filtering.\n\n## Implementation challenges\n\nTo implement this proposed webapp, we first need some sort of data type to\n_describe_ a schema. For simplicity, we'll only allow schemas to be strings,\nints, and products of strings and ints.\n\n```haskell\ndata Schema = SchString\n            | SchInt\n            | (:+:) Schema Schema\n\ninfixr 5 :+:\n\n-- A pair of an int and a string\nexampleSchema = SchString :+: SchInt\n\n-- A cart entry is just a pair of ints\ncartEntrySchema = SchInt :+: SchInt\n```\n\nThe challenge is in encoding a _representation_ of such a schema. An initial\nattempt may be something like\n\n```haskell\n-- Here, we just use a pair representation, but it can also be a record\ndata SchemaRepr = RString String\n                | RInt Int\n                | RPair SchemaRepr SchemaRepr\n\n-- How a cart entry might be stored\ncartEntryRepr :: SchemaRepr\ncartEntryRepr = RPair (RInt 5) (RInt 10)\n```\n\nbut this makes it quite tedious to write operations on a schema representation.\nSuppose we want to filter a list of records by some key.\n\n```haskell\nfilter :: Schema -\u003e SchemaRepr -\u003e [SchemaRepr] -\u003e [SchemaRepr]\n```\n\nHowever, to write such a function, we need to make the following assumptions:\n\n* the key is needs to be a value in the schema.\n* every record in the list needs to contain the given key\n* the type of the key in the record needs to match the type of the input key\n\nThese are \"obvious\" facts, since (hopefully) the data had been validated during\ninsertion. However, these facts are not reflected in the type -- all we have is\ninformation about \"some\" representation. Thus, we have no choice but to perform\nvalidation every single time `filter` or any other function that operates on the\ndata is called, even if the data is not modified.\n\nThe representation needs to be changed to reflect the fact that the\nrepresentation is _completely_ determined by the schema. It does not make sense\nto say \"I have a representation\"; rather, we must say \"I have a representation\nof this particular schema\". Thus, given some schema _value_, there should be\na corresponding representation _type_. This yields a function from values to types\n\n```haskell\ntype family SchemaRepr (s :: Schema) :: * where\n  SchemaRepr SchString = String\n  SchemaRepr SchInt = Int\n  SchemaRepr (a :+: b) = (SchemaRepr a, SchemaRepr b)\n```\n\nwhich, in theory, could let us write `filter` like\n\n```haskell\nfilter :: (s :: Schema) -\u003e SchemaRepr s -\u003e [SchemaRepr s] -\u003e [SchemaRepr s]\n```\n\nFor example, if our schema is `SchString :+: SchInt`, the type of `filter`\nbecomes\n\n```haskell\nfilter :: (SchString :+: SchInt) -\u003e (Int, String) -\u003e [(Int, String)] -\u003e [(Int, String)]\n```\n\nHowever, in practice, implementing such a function is either very difficult or\nimpossible in Haskell, because Haskell only partially supports dependent types\n(in its current state, at least; see \"Dependent Haskell\"). The purpose of the\ncode in this repository is to see how far we can go. See\n[Schema.hs](lib/Schema.hs) for concrete attempts at the problem.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnius%2Ftabler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechnius%2Ftabler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechnius%2Ftabler/lists"}