@fraczak/k 3.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BIG_PICTURE.md CHANGED
@@ -1,3 +1,4 @@
1
+ # Big Picture
1
2
 
2
3
  `Kernel-code` is an experiment aiming to define a "perfect programming language".
3
4
 
@@ -22,7 +23,7 @@ Types are defined by two constructs:
22
23
  1. Product
23
24
  2. Union (also called tagged or disjoint union)
24
25
 
25
- Given a set (possibly empty) of types `t1, t2, ..., tn` and a set of pairwise different tags (labels) `l1, l2, ..., ln`,
26
+ Given a finite (possibly empty) set of types `t1, t2, ..., tn` and a set of pairwise different tags (labels) `l1, l2, ..., ln`,
26
27
 
27
28
  - by `{ t1 l1, t2 l2, ..., tn ln }`, we define a _product type_ with exactly `n` projection functions `.l1`, `.l2`, `...`, `.ln`,
28
29
  mapping a value of type `{t1 l1, t2 l2, ..., tn ln}` into a value of type `t1`, `t2`, `...`, `tn`, respectively.
@@ -42,7 +43,6 @@ Some properties of these types:
42
43
  as a union type value (a variant).
43
44
  2. The empty product type `{}` has a single value, called the _unit_ and also denoted by `{}`, which has no fields.
44
45
  3. The empty union type `<>` has no values.
45
- 4. The product and union types form dual categories.
46
46
 
47
47
  ### Partial functions
48
48
 
@@ -64,7 +64,7 @@ empty composition, `()`, defines the identity function.
64
64
 
65
65
  ### Program
66
66
 
67
- A `kernel-core` program is a set of definitions of types and partial functions followed by an _expression_,
67
+ A `kernel-core` program is a set of definitions of types and partial functions, followed by an _expression_,
68
68
  which is the partial function the program defines.
69
69
 
70
70
  The language syntax is defined by:
@@ -97,7 +97,7 @@ The `typing` expression acts as a type annotation and can be seen as a 'filter',
97
97
  function defined only for the values of the corresponding type.
98
98
 
99
99
  That's it.
100
- No `int`, no `if` statement, no `loop`, no `throw`, no _closure_, no _annotations_, and no macros.
100
+ No `builtin` types, no `if` statement, no `loop`, no `throw`, no _closure_, no _annotations_, and no macros.
101
101
  Just types and partial functions.
102
102
 
103
103
  ### Normalization
@@ -170,41 +170,59 @@ Functions `bit0`, `bit1`, and `zero` are "constant polymorphic functions", meani
170
170
 
171
171
  - constant: if defined, they always return exactly the same value;
172
172
  - polymorphic: they are defined for more than one pair of input and output type: functions
173
- `bit1` and `bit2` are of type `X -> bit`, and `zero` is of type `X -> byte`, where `X`
174
- denotes an unconstrained type pattern.
173
+ `bit0` and `bit1` are of type `?X -> $bit`, and `zero` is of type `?X -> $byte`, where `?X`
174
+ denotes an unconstrained type pattern, also denoted as `?(...)`.
175
175
 
176
176
  Functions `inc` and `inc3` are (non-polymorphic) functions of types
177
- `byte -> { bit overflown, byte byte }` and
178
- `{ bit overflown, byte byte } -> { bit overflown, byte byte }`, respectively.
177
+ `$byte -> ${ bit overflown, byte byte }` and
178
+ `${ bit overflown, byte byte } -> ${ bit overflown, byte byte }`, respectively.
179
179
 
180
- Function `inc_o` is a polymorphic function of type patterns: `X -> Y` with
181
- following constraints:
180
+ Function `inc_o` is a polymorphic function of type patterns: `?X -> ${ byte byte, bit overflown }`
181
+ with following constraints:
182
182
 
183
- - `X` is a product type with at least two fields: `byte` and `overflown`, denoted by:
184
- > `?{ byte byte, Z overflown, ...}`;
185
- - `Z` is a type (union or product) with field `i` denoted by:
183
+ - `?X` is a product or union type with at least two fields: `byte` and `overflown`, denoted by:
184
+ > `?( $byte byte, Z overflown, ...)`;
185
+ - `?Z` is a type (union or product) with field `i` denoted by:
186
186
  > `?( V i, ...)`;
187
- - `V` is unconstrained;
188
- - `Y` is a product type with exactly two fields: `byte` and `overflown`, denoted by:
189
- > `?{ byte byte, bit overflown }`;
187
+ - `V` is unconstrained, denoted by `(...)`;
190
188
 
191
- The type pattern for `Y` is _singleton_, as only one type fits the pattern,
192
- whereas the other type patterns are not.
189
+ We could write it as: `?($byte byte, ((...) i, ...) overflown, ...)`.
193
190
 
194
- In this example, we were able to find the most general type pattern for all sub-expressions of the program. However, we still do not have a way to find the most general type patterns in the general case. This is work in progress.
191
+ The target type `${ byte byte, bit overflown }` was derived from pattern `?{ $byte byte, $bit overflown }`.
192
+ Such a pettern is called _singleton pattern_ as only one type fits the pattern.
195
193
 
196
194
  ## Universal Schema Registry
197
195
 
198
- Since the normalization process for types is fast and deterministic, we can build a universal schema registry that will store all invented types. The registry will be a key-value store, where the key is the hash of the normalized type, and the value is the normalized type itself.
196
+ Since the normalization process for types is fast and deterministic, we can build a universal
197
+ schema registry that will store all invented types.
198
+ The registry will be a key-value store, where the key is the hash of the normalized type,
199
+ and the value is the normalized type itself.
199
200
 
200
- All functions (non-polymorphic and polymorphic) will be renamed by the hash of their normalized definition and stored in the similar key-value store.
201
-
202
- Non-polymorphic functions can be easily indexed by the hashes of their input and output types so that we can quickly find the function we need, e.g., for autocomplition. Searching for polymorphic functions is more complicated, as we need to find the function whose input and output type patterns fit given types.
201
+ All functions (non-polymorphic and polymorphic) will be renamed by the hash of their normalized
202
+ definition and stored in the similar key-value store.
203
203
 
204
+ Non-polymorphic functions can be easily indexed by the hashes of their input and output types
205
+ so that we can quickly find the function we need.
206
+ Searching for polymorphic functions is more complicated, as we need to find the function
207
+ whose input and output type patterns fit given types.
204
208
 
205
209
  ## Serialization and Compilation
206
210
 
207
- The language is designed to transform _codes_, i.e., serialized typed values, i.e., trees accepted by a tree automaton, into other _codes_. The transformation is done by partial functions as defined in the language. The non-recursive and tail-recursive (and even some non-tail recursive) functions can be compiled into deterministic finite (pushdown) transducers.
211
+ The language is designed to transform _codes_, i.e., serialized typed values, i.e., trees
212
+ accepted by a tree automaton, into other _codes_.
213
+ The transformation is done by partial functions as defined in the language.
214
+ The non-recursive and tail-recursive (and even some non-tail recursive) functions can
215
+ be compiled into deterministic finite (pushdown) transducers.
216
+
217
+ ### AND-OR graphs for enconding and decoding codes
218
+
219
+ This could be generic and compact way to encode and decode typed values.
220
+ The idea is to use _prefix codes_ (a class of languages such that no word is a prefix of
221
+ another word) to encode the values.
222
+
223
+ ### RUST data structures
224
+
225
+ A type can be translated into a Rust data structure, and a function can be translated into a Rust function.
208
226
 
209
227
  ## Linking with Other Languages
210
228
 
@@ -0,0 +1,27 @@
1
+ import { t, in_out } from './index.mjs';
2
+ import fs from 'node:fs';
3
+ import assert from 'assert';
4
+
5
+ // t(fs.readFileSync("./Examples/blowup.k").toString("utf8"), (annotated) => {
6
+ // // console.log(JSON.stringify(annotated,null,2));
7
+ // const {input,output} = in_out(annotated);
8
+ // console.log(input);
9
+ // console.log(output);
10
+ // assert.equal(output.pattern, '<...>');
11
+ // assert.equal(input.pattern, '(...)');
12
+ // console.log("OK");
13
+ // });
14
+
15
+ t(`
16
+ B = {{} u} A;
17
+ A = < .a, .b A, .c {() z} A, B >;
18
+ A
19
+ `, (annotated) => {
20
+ // console.log(JSON.stringify(annotated,null,2));
21
+ const {input,output} = in_out(annotated);
22
+ console.log(input);
23
+ console.log(output);
24
+ assert.equal(output.type, annotated.representatives["bnat"]);
25
+ assert.equal(input.type, annotated.representatives["pair"]);
26
+ console.log("OK");
27
+ });
@@ -3,8 +3,10 @@ import fs from 'node:fs';
3
3
  import assert from 'assert';
4
4
 
5
5
  t(fs.readFileSync("./Examples/bnat-patterns.k").toString("utf8"), (annotated) => {
6
+ // console.log(JSON.stringify(annotated,null,2));
6
7
  const {input,output} = in_out(annotated);
7
- assert.equal(output.code, annotated.representatives["bnat"]);
8
- assert.equal(input.code, annotated.representatives["pair"]);
8
+ // console.log(output);
9
+ assert.equal(output.type, annotated.representatives["bnat"]);
10
+ assert.equal(input.type, annotated.representatives["pair"]);
9
11
  console.log("OK");
10
12
  });
@@ -4,7 +4,7 @@ import assert from 'assert';
4
4
  function is_result_code(code) {
5
5
  return function(annotated) {
6
6
  const {output} = in_out(annotated);
7
- assert.strictEqual(output.code, code);
7
+ assert.strictEqual(output.type, code);
8
8
  console.log('OK');
9
9
  }
10
10
  }
@@ -9,7 +9,32 @@ $b {1 one, () two, () c} .one
9
9
 
10
10
  `, (annotated) => {
11
11
  const {input,output} = in_out(annotated);
12
- assert.equal(input.code, annotated.representatives["b"]);
13
- assert.equal(output.code, "int");
12
+ // console.log({input,output});
13
+ assert.equal(input.type, annotated.representatives["b"]);
14
+ assert.equal(output.type, "int");
14
15
  console.log("OK");
15
- });
16
+ });
17
+
18
+ t(`
19
+ $a = < int true, bool false > ;
20
+ $a .true
21
+ `, (annotated) => {
22
+ const {input,output} = in_out(annotated);
23
+ console.log({input,output});
24
+ });
25
+
26
+ t(`
27
+ x = .x;
28
+ xy = x.y;
29
+ f = {
30
+ { {5 y} x} xy i,
31
+ { {true y} x} xy b
32
+ };
33
+ f .b
34
+ `, (annotated) => {
35
+ const {input,output} = in_out(annotated);
36
+ console.log({input,output});
37
+ assert.equal(input.pattern, '(...)');
38
+ assert.equal(output.type, "bool");
39
+ console.log("OK");
40
+ });
@@ -1,8 +1,8 @@
1
1
  import { annotate } from "../index.mjs";
2
2
 
3
3
  function in_out(annotated) {
4
- const [input,output] = annotated.rels.__main__[0].patterns.map( (pat) =>
5
- annotated.patternNodes[pat]
4
+ const [input,output] = annotated.rels.__main__.def.patterns.map( (pat) =>
5
+ annotated.rels.__main__.typePatternGraph.get_pattern(pat)
6
6
  );
7
7
  return { input, output };
8
8
  }
@@ -3,7 +3,7 @@ import assert from 'assert';
3
3
 
4
4
  t(` < .a, () > `, (annotated) => {
5
5
  const {input,output} = in_out(annotated);
6
- assert.equal(input.type, "union");
6
+ assert.equal(input.pattern, "(...)");
7
7
  assert.equal(output, input);
8
8
  console.log("OK");
9
9
  });
@@ -4,7 +4,7 @@ import assert from 'assert';
4
4
 
5
5
  t(fs.readFileSync("./Examples/nat.k").toString("utf8"), (annotated) => {
6
6
  const {input,output} = in_out(annotated);
7
- assert.equal(output.code, annotated.representatives["nat"]);
8
- assert.equal(input.code, annotated.representatives["nat"]);
7
+ assert.equal(output.type, annotated.representatives["nat"]);
8
+ assert.equal(input.type, annotated.representatives["nat"]);
9
9
  console.log("OK");
10
10
  });
@@ -8,7 +8,7 @@ t(`$b = < {} true, {} false > ;
8
8
  not not
9
9
  `, (annotated) => {
10
10
  const {input,output} = in_out(annotated);
11
- assert.equal(input.code, annotated.representatives["b"]);
12
- assert.equal(output.code, annotated.representatives["b"]);
11
+ assert.equal(input.type, annotated.representatives["b"]);
12
+ assert.equal(output.type, annotated.representatives["b"]);
13
13
  console.log("OK");
14
14
  });
@@ -1,10 +1,13 @@
1
1
  import { t, in_out } from './index.mjs';
2
2
  import assert from 'assert';
3
+ import hash from "../hash.mjs";
4
+
5
+ const unitCode = hash('$C0={};');
3
6
 
4
7
  t('{}', (annotated) => {
5
8
  const {input,output} = in_out(annotated);
6
- assert.equal(input.type, null);
7
- assert.equal(output.code, annotated.representatives["{}"]);
9
+ assert.equal(input.pattern, '(...)');
10
+ assert.equal(output.type, unitCode);
8
11
  console.log("OK");
9
12
  });
10
13
 
@@ -12,10 +15,11 @@ t(`
12
15
  $b = < {} true, {} false > ;
13
16
  $pair = { b one, b two } ;
14
17
 
15
- {() one, () two} $pair
18
+ {() one, () two} $pair
16
19
  `, (annotated) => {
17
20
  const {input,output} = in_out(annotated);
18
- assert.equal(input.code, annotated.representatives["b"]);
19
- assert.equal(output.code, annotated.representatives["pair"]);
21
+
22
+ assert.equal(input.type, annotated.representatives["b"]);
23
+ assert.equal(output.type, annotated.representatives["pair"]);
20
24
  console.log("OK");
21
25
  });
@@ -8,7 +8,7 @@ t(`
8
8
  rlz
9
9
  `, (annotated) => {
10
10
  const {input,output} = in_out(annotated);
11
- assert.equal(output.code, annotated.representatives["nat"]);
12
- assert.equal(input.code, annotated.representatives["nat"]);
11
+ assert.equal(output.type, annotated.representatives["nat"]);
12
+ assert.equal(input.type, annotated.representatives["nat"]);
13
13
  console.log("OK");
14
14
  });
@@ -3,26 +3,17 @@ import assert from 'assert';
3
3
 
4
4
  t('[]', (annotated) => {
5
5
  const {input,output} = in_out(annotated);
6
- assert.equal(input.type, null);
7
- assert.equal(output.type, "vector");
6
+ assert.equal(input.pattern, '(...)');
7
+ assert.equal(output.pattern, "[]");
8
8
  console.log("OK");
9
9
  });
10
10
 
11
- t(`$int [ $int true, false ]`, (annotated) => {
11
+ t("$int [ $int true, false ] .0", (annotated) => {
12
12
  const {input,output} = in_out(annotated);
13
- assert.equal(input.code, "int");
14
- assert.equal(output.type, "vector");
13
+ assert.equal(input.type, "int");
14
+ assert.equal(output.type, "bool");
15
15
  console.log("OK");
16
16
  });
17
17
 
18
- t(`$int [ $int true, false ] .0`, (annotated) => {
19
- const {input,output} = in_out(annotated);
20
- assert.equal(input.code, "int");
21
- assert.equal(output.code, "bool");
22
- console.log("OK");
23
- });
24
-
25
- // assert.throws(() => t(`$int [ $int true, 12 ]`), /Error/);
26
- // console.log("OK");
27
-
28
- // t(`[]`);
18
+ assert.throws(() => t(`$int [ $int true, 12 ]`), /Error/);
19
+ console.log("OK");
package/Filters/bug.k ADDED
@@ -0,0 +1,3 @@
1
+ swap = ?{<...> x, Y y} {.x y, .y x} .y ?Y;
2
+
3
+ {"asd" x, "a" y} swap .asd
@@ -0,0 +1,15 @@
1
+ remap = ?T {.x y} ?T ?<X z, ...>;
2
+ swap = ?T {.x y, .y x} ?T;
3
+
4
+ $t = <int x, int y>;
5
+
6
+ $t2 = <int x, int y, int z>;
7
+
8
+ -- {12 x} $t remap $t
9
+ -- {[1,2] x} remap
10
+ -- {"ala" x, 3 y} swap
11
+
12
+ {
13
+ {12 x} $t2 remap $t2 remap,
14
+ {"ala" x, "ola" y} swap
15
+ }
@@ -0,0 +1,9 @@
1
+ f1 = ?{T x, [T] y};
2
+ f2 = ?{[R] x, R y};
3
+
4
+ swap = f1 f2 {.x y, .y x};
5
+
6
+
7
+ {[] x, [[[[]]]] y} swap
8
+
9
+ -- shoud_work
package/Graph.mjs ADDED
@@ -0,0 +1,95 @@
1
+ class Graph {
2
+ constructor(edges, vertices = {}) {
3
+ this.edges = edges;
4
+ this.vertices = vertices;
5
+ for (const dir of ['src', 'dst']) {
6
+ this[dir] = {};
7
+ for (let i=0; i < edges.length; i++) {
8
+ const e = edges[i];
9
+ if (vertices[e[dir]] == undefined) {
10
+ vertices[e[dir]] = {
11
+ _rem: "discovered"
12
+ };
13
+ }
14
+ if (this[dir][e[dir]] == null) {
15
+ this[dir][e[dir]] = [];
16
+ }
17
+ this[dir][e[dir]].push(i);
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ function sccs(graph) {
24
+ const stack = [];
25
+ const disc = {};
26
+ const low = {};
27
+ const inStack = {};
28
+ const sccs = [];
29
+ let time = 0;
30
+
31
+ const dfs = (u) => {
32
+ disc[u] = low[u] = time++;
33
+ stack.push(u);
34
+ inStack[u] = true;
35
+
36
+ for (const e of graph.src[u] || []) {
37
+ const v = graph.edges[e].dst;
38
+ if (disc[v] == undefined) {
39
+ dfs(v);
40
+ low[u] = Math.min(low[u], low[v]);
41
+ } else if (inStack[v]) {
42
+ low[u] = Math.min(low[u], disc[v]);
43
+ }
44
+ }
45
+
46
+ if (low[u] === disc[u]) {
47
+ const scc = [];
48
+ let w;
49
+ do {
50
+ w = stack.pop();
51
+ inStack[w] = false;
52
+ scc.push(w);
53
+ } while (w !== u);
54
+ sccs.push(scc);
55
+ }
56
+ };
57
+
58
+ for (const x in graph.vertices) {
59
+ if (disc[x] == undefined) {
60
+ dfs(x);
61
+ }
62
+ }
63
+ return sccs;
64
+ };
65
+
66
+ function topoOrder(graph){
67
+ const starts = [];
68
+ const result = [];
69
+ const inDegree = {};
70
+ for (const v in graph.vertices) {
71
+ const degree = graph.dst[v]?.length || 0;
72
+ if (degree == 0) {
73
+ starts.push(v);
74
+ } else {
75
+ inDegree[v] = degree;
76
+ }
77
+ }
78
+
79
+ let x;
80
+ while (x = starts.pop()) {
81
+ result.push(x);
82
+ const outs = graph.src[x] || [];
83
+ for (const e of outs) {
84
+ const y = graph.edges[e].dst;
85
+ inDegree[y]--;
86
+ if (inDegree[y] == 0) {
87
+ starts.push(y);
88
+ }
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+
94
+ export { Graph, sccs, topoOrder };
95
+ export default { Graph, sccs, topoOrder };
package/README.md CHANGED
@@ -87,7 +87,7 @@ supported.
87
87
 
88
88
  ### Basic extensions
89
89
 
90
- #### Constants, i.e., literals for `strings`, `integers`, `booleans`, and `null`
90
+ #### Constants, i.e., literals for `string`, `int`, `bool`, and `null`
91
91
 
92
92
  A constant defines a function which ignores its argument and produces
93
93
  the constant value. E.g.:
@@ -95,7 +95,7 @@ the constant value. E.g.:
95
95
  {123 int, "kScript" str, true bool, null null} :
96
96
  "any" --> {"int":123,"str":"kScript","bool":true,"null":null}
97
97
 
98
- Those values, i.e., `strings`, `integers`, `booleans`, and `null`, admit the
98
+ Those values, i.e., _strings_, _integers_, _booleans_, and _null_, admit the
99
99
  projection to the canonicat string representation of the value, e.g.:
100
100
 
101
101
  .2 :
@@ -176,7 +176,6 @@ is not supported in _code derivation_ (explained below).
176
176
  "2.12" --> 2.12
177
177
  "[1,2.11,0.3e-32]" --> [1,2.11,3e-33]
178
178
 
179
-
180
179
  - other predefined parial functions are: `DIV`, `FDIV`, `CONS`,
181
180
  `SNOC`, `toDateMsec`, `toDateStr`, and `_log!`.
182
181
 
@@ -185,10 +184,11 @@ is not supported in _code derivation_ (explained below).
185
184
  ### Function and code (i.e., _type_) definitions
186
185
 
187
186
  dec = [(),-1] PLUS;
188
- max = <SNOC [.0, .1 max] <GT.0, .1>, .0> ;
187
+ max = <SNOC [.car, .cdr max] <GT.0, .1>, .0> ;
189
188
  factorial = < [(),0] GT .0 [dec factorial, ()] TIMES, 1 >;
190
189
 
191
- ### Codes (schemas, i.e., _non-functional types_)
190
+ ### Codes (schemas, i.e., _types_)
191
+
192
192
  _Codes_ (prefixed by `$`) can be defined by tagged union and product. E.g.:
193
193
 
194
194
 
@@ -256,12 +256,15 @@ fail, indicating that the script is invalid. In some other cases the
256
256
  code derivation can succeed even to the point of reducig every pattern
257
257
  to a single code making the program fully annotated by codes.
258
258
 
259
- For now, `code derivation` is supported by the core language, i.e., `composition`,
260
- `projection` and `union` with product and tagged union types.
259
+ The language `k` supports _type patterns_, which is used for type inference, however the patterns are not concidered in the evaluation of the program.
260
+
261
+ ```k
262
+ tree_pattern = ?<(...) leaf, {T left, T right} tree> = T;
263
+ ```
261
264
 
262
265
  ---
263
266
 
264
- ### List comprehension on vectors (experimental!)
267
+ ### List comprehension
265
268
 
266
269
  A vector can be "open" by PIPE (`|`) operator so the following partial function is applied to
267
270
  each element of the vector one by one, yielding another open value. An open value can be "closed",
@@ -355,11 +358,11 @@ times value `x` appears in the list. (see `Examples/list-comprehension.k` for a
355
358
  Another example could be finding the biggest (max) value in a vector:
356
359
 
357
360
  max = <
358
- SNOC # [x0, x1, x2, ...] --> [x0, [x1, x2, ...]]
359
- [.0, .1 max] # [x0, [x1, x2, ...]] --> [x0, max(x1,x2,...)], i.e., recursive call
360
- <GT .0, .1> # if x0 > max(x1,x2,...) then x0 else max(x1,x2,...)
361
- , # when SNOC is not defined, i.e., if the input vector has one element:
362
- .0 # [x0] --> x0
361
+ SNOC # [x0, x1, x2, ...] --> {x0 car, [x1, x2, ...] cdr}
362
+ [.car, .cdr max] # [x0, [x1, x2, ...]] --> [x0, max(x1,x2,...)], i.e., recursive call
363
+ <GT .0, .1> # if x0 > max(x1,x2,...) then x0 else max(x1,x2,...)
364
+ , # when SNOC is not defined, i.e., if the input vector has one element:
365
+ .0 # [x0] --> x0
363
366
  >;
364
367
  max
365
368
 
@@ -468,10 +471,10 @@ For example:
468
471
 
469
472
  1.
470
473
  curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | jq '.'
471
- curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | k '()' -1
474
+ curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | k '()' -1
472
475
  2.
473
476
  curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | jq '.[0]'
474
- curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | k '.0' -1
477
+ curl 'https://api.github.com/repositories/5101141/commits?per_page=5' | k '.0' -1
475
478
  3.
476
479
  jq '.[0] | {message: .commit.message, name: .commit.committer.name}'
477
480
  k '.0 {.commit.message message, .commit.committer.name name}' -1