From ceffbab4c2f9200104f633a8119090f1e6f18bae Mon Sep 17 00:00:00 2001 From: Rob Durst Date: Sun, 16 Jul 2023 15:41:12 -0600 Subject: [PATCH 1/5] init commit --- src/js/trove/global.js | 22 +++++++++++++++++++--- tests/type-check/good/table.arr | 27 ++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/js/trove/global.js b/src/js/trove/global.js index 72c42b2ae0..85bf3175ed 100644 --- a/src/js/trove/global.js +++ b/src/js/trove/global.js @@ -23,7 +23,14 @@ "tve": ["tid", "e"], "Equality": { tag: "name", origin: { "import-type": "uri", uri: "builtin://equality" }, - name: "EqualityResult" } + name: "EqualityResult" }, + "Row": { tag: "name", + origin: { "import-type": "uri", uri: "builtin://global" }, + name: "Row" }, + "Table": { tag: "name", + origin: { "import-type": "uri", uri: "builtin://global" }, + name: "Table" }, + "Col": "tany" }, values: { "print": ["forall", "a", ["arrow", ["tva"], "tva"]], @@ -524,9 +531,18 @@ "_greaterequal": ["arrow", ["String"], "Boolean"] }], "Table": ["data", "Table", [], [], { - "length": ["arrow", [], "Number"] + "length": ["arrow", [], "Number"], + "add-column": ["arrow", ["String", ["List", "Col"]], "Table"], + "row-n": ["arrow", ["Number"], "Row"], + "column": ["arrow", ["String"], ["List", "Col"]], + "select-columns": ["arrow", [["List", "String"]], "Table"], + "all-columns": ["arrow", [], ["List", ["List", "Col"]]], + "all-rows": ["arrow", [], ["List", "Row"]], + "column-names": ["arrow", [], ["List", "String"]], + }], + "Row": ["data", "Row", [], [], { + "get-column-names": ["arrow", [], ["List", "String"]], }], - "Row": ["data", "Row", [], [], { }], "Function": ["data", "Function", [], [], {}], "Boolean": ["data", "Boolean", [], [], {}], "Object": ["data", "Object", [], [], {}], diff --git a/tests/type-check/good/table.arr b/tests/type-check/good/table.arr index c55648c58f..acddcae638 100644 --- a/tests/type-check/good/table.arr +++ b/tests/type-check/good/table.arr @@ -10,4 +10,29 @@ tbl = table: name, age row: "Bob", 12 row: "Alice", 15 row: "Eve", 13 -end \ No newline at end of file +end + +extended-tbl-3-rows = tbl.add-column("gender", [list: "man", "lady", "lady"]) + +check "adding columns does not add rows": + extended-tbl-3-rows.length() is tbl.length() +end + +row-1 = extended-tbl-3-rows.row-n(1) +row-1-column-names = row-1.get-column-names() +age-column = extended-tbl-3-rows.column("age") + +check "can select all columns": + all-columns-tbl = tbl.all-columns() + all-columns-tbl-selected = tbl.select-columns([list: "name", "age"]) + + all-columns-tbl is all-columns-tbl-selected +end + +check "all rows grabs all rows": + tbl.all-rows().length() is tbl.length() +end + +check "fetchs all table names": + tbl.column-names() is [list: "name", "age"] +end From c475de732a20c5cd58fd8763a6a122218a5db934 Mon Sep 17 00:00:00 2001 From: Rob Durst Date: Wed, 19 Jul 2023 17:56:43 -0600 Subject: [PATCH 2/5] add some more coverage --- src/js/trove/global.js | 5 +++++ tests/type-check/bad/table.arr | 9 ++++++++ tests/type-check/good/table.arr | 38 ++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 tests/type-check/bad/table.arr diff --git a/src/js/trove/global.js b/src/js/trove/global.js index 85bf3175ed..7378220bcf 100644 --- a/src/js/trove/global.js +++ b/src/js/trove/global.js @@ -33,6 +33,8 @@ "Col": "tany" }, values: { + "table-from-column": ["arrow", ["Col", ["List", "Col"]], "Table"], + "print": ["forall", "a", ["arrow", ["tva"], "tva"]], "test-print": ["forall", "a", ["arrow", ["tva"], "tva"]], "print-error": ["forall", "a", ["arrow", ["tva"], "tva"]], @@ -539,9 +541,12 @@ "all-columns": ["arrow", [], ["List", ["List", "Col"]]], "all-rows": ["arrow", [], ["List", "Row"]], "column-names": ["arrow", [], ["List", "String"]], + "build-column": ["arrow", ["String", ["arrow", ["Row"], "Col"]], "Table"], }], "Row": ["data", "Row", [], [], { "get-column-names": ["arrow", [], ["List", "String"]], + "get-value": ["arrow", ["String"], "Col"], + "get": ["arrow", ["String"], ["Option", "Col"]], }], "Function": ["data", "Function", [], [], {}], "Boolean": ["data", "Boolean", [], [], {}], diff --git a/tests/type-check/bad/table.arr b/tests/type-check/bad/table.arr new file mode 100644 index 0000000000..30c5167738 --- /dev/null +++ b/tests/type-check/bad/table.arr @@ -0,0 +1,9 @@ +email :: Table = + table: sender, subject + row: "Matthias", "hi" + row: "Kathi", "foo" + row: "Joe", "bar" + row: "Matthias", "bye" + end + + all-columns-tbl-selected = tbl.select-columns([list: 10, 11]) diff --git a/tests/type-check/good/table.arr b/tests/type-check/good/table.arr index acddcae638..f56ad040b2 100644 --- a/tests/type-check/good/table.arr +++ b/tests/type-check/good/table.arr @@ -1,3 +1,5 @@ +import tables as T + email :: Table = table: sender, subject row: "Matthias", "hi" @@ -22,13 +24,18 @@ row-1 = extended-tbl-3-rows.row-n(1) row-1-column-names = row-1.get-column-names() age-column = extended-tbl-3-rows.column("age") -check "can select all columns": - all-columns-tbl = tbl.all-columns() - all-columns-tbl-selected = tbl.select-columns([list: "name", "age"]) - - all-columns-tbl is all-columns-tbl-selected +check "can extract value": + row-1.get-value("age") is 15 end +# TODO: doesn't work +# check "can create a table from a column": + # T.table-from-column("foo", [list: "bar", "baz"]) satisfies is-table +# end + +all-columns-tbl = tbl.all-columns() +all-columns-tbl-selected = tbl.select-columns([list: "name", "age"]) + check "all rows grabs all rows": tbl.all-rows().length() is tbl.length() end @@ -36,3 +43,24 @@ end check "fetchs all table names": tbl.column-names() is [list: "name", "age"] end + +check "can build-column": + foods = table: name, grams, calories + row: "Fries", 200, 500 + row: "Milkshake", 400, 600 + end + foods-with-cpg = table: name, grams, calories, cal-per-gram + row: "Fries", 200, 500, 500/200 + row: "Milkshake", 400, 600, 600/400 + end + + fun add-cpg(r :: Row) -> Boolean: +# fun add-cpg(r :: Row) -> Number: + # r.get-value("calories") / r.get-value("grams") + true + end + + foods.build-column("cal-per-gram", add-cpg) is foods-with-cpg + + foods-with-cpg satisfies is-table +end From 67e81a9e0200d01251091352da003888901484ff Mon Sep 17 00:00:00 2001 From: Rob Durst Date: Thu, 20 Jul 2023 07:48:56 -0600 Subject: [PATCH 3/5] add more type signatures --- src/js/trove/global.js | 26 +++++++++++++++++++++++++- tests/type-check/good/table.arr | 14 ++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/js/trove/global.js b/src/js/trove/global.js index 7378220bcf..7fdad3e94d 100644 --- a/src/js/trove/global.js +++ b/src/js/trove/global.js @@ -542,11 +542,35 @@ "all-rows": ["arrow", [], ["List", "Row"]], "column-names": ["arrow", [], ["List", "String"]], "build-column": ["arrow", ["String", ["arrow", ["Row"], "Col"]], "Table"], + "increasing-by": ["arrow", ["String"], "Table"], + "decreasing-by": ["arrow", ["String"], "Table"], + "order-by": ["arrow", ["String", "Boolean"], "Table"], + // incorrect: column names must all match, order does not matter + "stack": ["arrow", ["Table"], "Table"], + "empty": ["arrow", [], "Table"], + "drop": ["arrow", ["String"], "Table"], + "rename-column": ["arrow", ["String", "String"], "Table"], + + // order-by-columns + // multi-order + // add-row + // column-n + // reduce + // filter-by + // filter + // transform-column + // _column-index + // _no-column + // _equals + // _output + // row + // new-row }], "Row": ["data", "Row", [], [], { "get-column-names": ["arrow", [], ["List", "String"]], "get-value": ["arrow", ["String"], "Col"], - "get": ["arrow", ["String"], ["Option", "Col"]], + // _output + // _equals }], "Function": ["data", "Function", [], [], {}], "Boolean": ["data", "Boolean", [], [], {}], diff --git a/tests/type-check/good/table.arr b/tests/type-check/good/table.arr index f56ad040b2..c312a5336c 100644 --- a/tests/type-check/good/table.arr +++ b/tests/type-check/good/table.arr @@ -64,3 +64,17 @@ check "can build-column": foods-with-cpg satisfies is-table end + +tbl.increasing-by("age") +tbl.decreasing-by("age") +tbl.order-by("age", false) + +tbl-2 = table:age, name + row: 35, "age" +end + +stacked-tbl = tbl.stack(tbl-2) +empty-tbl = tbl.empty() + +dropped-age-tbl = tbl.drop("age") +renamed-age-tbl = tbl.rename-column("age", "years-old") From dd15f3c0283d80c30540899e838e696c5e58c2b4 Mon Sep 17 00:00:00 2001 From: Rob Durst Date: Sat, 22 Jul 2023 19:40:27 -0600 Subject: [PATCH 4/5] further additions --- src/js/trove/global.js | 38 +++++++++++++++++++++++++-------- tests/type-check/good/table.arr | 17 ++++++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/js/trove/global.js b/src/js/trove/global.js index 7fdad3e94d..09776af2b6 100644 --- a/src/js/trove/global.js +++ b/src/js/trove/global.js @@ -532,6 +532,18 @@ "_greaterthan": ["arrow", ["String"], "Boolean"], "_greaterequal": ["arrow", ["String"], "Boolean"] }], + // Issues and things that make me uncomfortable: + // * sometimes order matters and we can't enforce it + // * sometimes column names must match and we can't enforce it + // * sometimes we want to ensure a column being passed in exists in the schema and we can't enforce it + // * would be nice to have a way to express column uniqueness (i.e. add-column) + // * Col is an any (even though we do have the type info available), so: + // * when passing in functions, these functions cannot really be typed + // * when returning from these methods we lose type information + // + // Things I do like about this: + // * thus far it "feels right" to return Table and Row. These types are cognitively simple to understand. + // "Table": ["data", "Table", [], [], { "length": ["arrow", [], "Number"], "add-column": ["arrow", ["String", ["List", "Col"]], "Table"], @@ -550,25 +562,33 @@ "empty": ["arrow", [], "Table"], "drop": ["arrow", ["String"], "Table"], "rename-column": ["arrow", ["String", "String"], "Table"], + "column-n": ["arrow", ["Number"], "Col"], + "filter": ["arrow", [["arrow", ["Col"], "Boolean"]], "Table"], + "filter-by": ["arrow", ["String", ["arrow", ["Col"], "Boolean"]], "Table"], + // incorrect: column names must all match and order matters + "add-row": ["arrow", ["Row"], "Table"], - // order-by-columns - // multi-order - // add-row - // column-n - // reduce - // filter-by - // filter + // TODO: figure out how to properly type these variadic function + // row + // new-row + + // TODO: finish implementing types for these + // order-by-columns - how to type a Tuple? + // multi-order - how to type an array of two element arrays? + // reduce - how to type a Reducer? // transform-column + + // TODO: implement types for internals // _column-index // _no-column // _equals // _output - // row - // new-row }], "Row": ["data", "Row", [], [], { "get-column-names": ["arrow", [], ["List", "String"]], "get-value": ["arrow", ["String"], "Col"], + + // TODO: implement types for internals // _output // _equals }], diff --git a/tests/type-check/good/table.arr b/tests/type-check/good/table.arr index c312a5336c..bdd1912bb7 100644 --- a/tests/type-check/good/table.arr +++ b/tests/type-check/good/table.arr @@ -1,4 +1,5 @@ import tables as T +import string-dict as SD email :: Table = table: sender, subject @@ -60,7 +61,7 @@ check "can build-column": true end - foods.build-column("cal-per-gram", add-cpg) is foods-with-cpg + foods.build-column("cal-per-gram", add-cpg) foods-with-cpg satisfies is-table end @@ -78,3 +79,17 @@ empty-tbl = tbl.empty() dropped-age-tbl = tbl.drop("age") renamed-age-tbl = tbl.rename-column("age", "years-old") + +column-1 = tbl.column-n(1) + +fun allTrue(x) -> Boolean: + true +end + +tbl.filter(allTrue) +tbl.filter-by("age", allTrue) + +tbl-with-extra-row = tbl.add-row([T.raw-row: {"name"; "Paul"}, {"age"; 10}]) + +tbl.row(10, "Bar") +extended-tbl-3-rows.row(10, "Bar", "Foo") From 1f5d311f8fc5f76c11b86e05426194bf4ddba8b0 Mon Sep 17 00:00:00 2001 From: Rob Durst Date: Mon, 24 Jul 2023 10:56:07 -0600 Subject: [PATCH 5/5] re-org so easier to read --- src/js/trove/global.js | 3 + tests/type-check/bad/table.arr | 3 +- tests/type-check/good/table.arr | 119 +++++++++++++++----------------- 3 files changed, 61 insertions(+), 64 deletions(-) diff --git a/src/js/trove/global.js b/src/js/trove/global.js index 09776af2b6..225bb8e03b 100644 --- a/src/js/trove/global.js +++ b/src/js/trove/global.js @@ -583,6 +583,9 @@ // _no-column // _equals // _output + + // TODO: constructors + // raw-row }], "Row": ["data", "Row", [], [], { "get-column-names": ["arrow", [], ["List", "String"]], diff --git a/tests/type-check/bad/table.arr b/tests/type-check/bad/table.arr index 30c5167738..d5e41731a2 100644 --- a/tests/type-check/bad/table.arr +++ b/tests/type-check/bad/table.arr @@ -6,4 +6,5 @@ email :: Table = row: "Matthias", "bye" end - all-columns-tbl-selected = tbl.select-columns([list: 10, 11]) +# select-columns :: List -> Table +all-columns-tbl-selected = tbl.select-columns([list: 10, 11]) diff --git a/tests/type-check/good/table.arr b/tests/type-check/good/table.arr index bdd1912bb7..7a00f7d135 100644 --- a/tests/type-check/good/table.arr +++ b/tests/type-check/good/table.arr @@ -1,13 +1,6 @@ import tables as T -import string-dict as SD -email :: Table = - table: sender, subject - row: "Matthias", "hi" - row: "Kathi", "foo" - row: "Joe", "bar" - row: "Matthias", "bye" - end +#=============== Set Up ================== # tbl = table: name, age row: "Bob", 12 @@ -15,81 +8,81 @@ tbl = table: name, age row: "Eve", 13 end -extended-tbl-3-rows = tbl.add-column("gender", [list: "man", "lady", "lady"]) +tbl-2 = table: name, age + row: "Rob", 14 +end -check "adding columns does not add rows": - extended-tbl-3-rows.length() is tbl.length() +fun allTrue(x) -> Boolean: + true end -row-1 = extended-tbl-3-rows.row-n(1) -row-1-column-names = row-1.get-column-names() -age-column = extended-tbl-3-rows.column("age") +#=============== Table ================== # -check "can extract value": - row-1.get-value("age") is 15 -end +# length :: () -> Number +tbl.length() -# TODO: doesn't work -# check "can create a table from a column": - # T.table-from-column("foo", [list: "bar", "baz"]) satisfies is-table -# end +# add-column :: (String, List) -> Table +tbl.add-column("gender", [list: "man", "lady", "lady"]) -all-columns-tbl = tbl.all-columns() -all-columns-tbl-selected = tbl.select-columns([list: "name", "age"]) +# row-n :: Number -> Row +row-1 = tbl.row-n(1) -check "all rows grabs all rows": - tbl.all-rows().length() is tbl.length() -end +# column :: (String) -> List +age-column = tbl.column("age") -check "fetchs all table names": - tbl.column-names() is [list: "name", "age"] -end +# select-columns :: (List) -> Table +tbl.select-columns([list: "name", "age"]) -check "can build-column": - foods = table: name, grams, calories - row: "Fries", 200, 500 - row: "Milkshake", 400, 600 - end - foods-with-cpg = table: name, grams, calories, cal-per-gram - row: "Fries", 200, 500, 500/200 - row: "Milkshake", 400, 600, 600/400 - end - - fun add-cpg(r :: Row) -> Boolean: -# fun add-cpg(r :: Row) -> Number: - # r.get-value("calories") / r.get-value("grams") - true - end - - foods.build-column("cal-per-gram", add-cpg) - - foods-with-cpg satisfies is-table -end +# all-columns :: () -> List> +tbl.all-columns() + +# all-rows :: () -> List +tbl.all-rows() + +# column-names :: () -> List +tbl.column-names() + +# build-column :: (String, (Row -> Col)) -> Table +tbl.build-column("is-cool", allTrue) +# increasing-by :: (String) -> Table tbl.increasing-by("age") + +# decreasing-by :: (String) -> Table tbl.decreasing-by("age") + +# order-by :: (String, Boolean) -> Table tbl.order-by("age", false) -tbl-2 = table:age, name - row: 35, "age" -end +# stack :: (Table) -> Table +tbl.stack(tbl-2) -stacked-tbl = tbl.stack(tbl-2) -empty-tbl = tbl.empty() +# empty :: () -> Table +tbl.empty() -dropped-age-tbl = tbl.drop("age") -renamed-age-tbl = tbl.rename-column("age", "years-old") +# drop :: (String) -> Table +tbl.drop("age") -column-1 = tbl.column-n(1) +# rename-column :: (String, String) -> Table +tbl.rename-column("age", "years-old") -fun allTrue(x) -> Boolean: - true -end +# column-n :: (Number) -> Col +tbl.column-n(1) +# filter :: (Col -> Boolean) -> Table tbl.filter(allTrue) + +# filter-by :: (String, (Col -> Boolean)) -> Table tbl.filter-by("age", allTrue) -tbl-with-extra-row = tbl.add-row([T.raw-row: {"name"; "Paul"}, {"age"; 10}]) +# TODO: after fix raw-row, use a uniquely constructed row here +# add-row :: Row -> Table +tbl.add-row(row-1) + +#=============== Row ================== # + +# get-column-names :: () -> List +row-1-column-names = row-1.get-column-names() -tbl.row(10, "Bar") -extended-tbl-3-rows.row(10, "Bar", "Foo") +# get-value :: (String) -> Col +row-1.get-value("age")