Skip to content

Commit 8d3b2e3

Browse files
authored
Fix list accumulate (#1463)
* Fix accumulate and add tests for it * Update gitignore to ignore yarn errors
1 parent 7161478 commit 8d3b2e3

File tree

5 files changed

+114
-11
lines changed

5 files changed

+114
-11
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ coverage/
1414
.vscode/
1515
tsconfig.tsbuildinfo
1616
test-report.html
17+
18+
yarn-error.log

src/stdlib/__tests__/__snapshots__/list.ts.snap

+54
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,32 @@ Object {
286286
}
287287
`;
288288

289+
exports[`accumulate works from right to left: expectResult 1`] = `
290+
Object {
291+
"alertResult": Array [],
292+
"code": "accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));",
293+
"displayResult": Array [],
294+
"numErrors": 0,
295+
"parsedErrors": "",
296+
"result": "4321",
297+
"resultStatus": "finished",
298+
"visualiseListResult": Array [],
299+
}
300+
`;
301+
302+
exports[`accumulate works properly: expectResult 1`] = `
303+
Object {
304+
"alertResult": Array [],
305+
"code": "accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));",
306+
"displayResult": Array [],
307+
"numErrors": 0,
308+
"parsedErrors": "",
309+
"result": 10,
310+
"resultStatus": "finished",
311+
"visualiseListResult": Array [],
312+
}
313+
`;
314+
289315
exports[`accumulate: expectResult 1`] = `
290316
Object {
291317
"alertResult": Array [],
@@ -789,6 +815,34 @@ Object {
789815
}
790816
`;
791817
818+
exports[`length works with empty lists: expectResult 1`] = `
819+
Object {
820+
"alertResult": Array [],
821+
"code": "const xs = list();
822+
length(xs);",
823+
"displayResult": Array [],
824+
"numErrors": 0,
825+
"parsedErrors": "",
826+
"result": 0,
827+
"resultStatus": "finished",
828+
"visualiseListResult": Array [],
829+
}
830+
`;
831+
832+
exports[`length works with populated lists: expectResult 1`] = `
833+
Object {
834+
"alertResult": Array [],
835+
"code": "const xs = list(1,2,3,4);
836+
length(xs);",
837+
"displayResult": Array [],
838+
"numErrors": 0,
839+
"parsedErrors": "",
840+
"result": 4,
841+
"resultStatus": "finished",
842+
"visualiseListResult": Array [],
843+
}
844+
`;
845+
792846
exports[`list creates list: expectResult 1`] = `
793847
Object {
794848
"alertResult": Array [],

src/stdlib/__tests__/list.ts

+41
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,47 @@ test('list_to_string', () => {
246246
).toMatchInlineSnapshot(`"[1,[2,[3,null]]]"`)
247247
})
248248

249+
describe('accumulate', () => {
250+
test('works properly', () => {
251+
return expectResult(
252+
stripIndent`
253+
accumulate((curr, acc) => curr + acc, 0, list(2, 3, 4, 1));
254+
`,
255+
{ chapter: Chapter.SOURCE_2, native: true }
256+
).toMatchInlineSnapshot(`10`)
257+
})
258+
259+
it('works from right to left', () => {
260+
return expectResult(
261+
stripIndent`
262+
accumulate((curr, acc) => curr + acc, '1', list('4','3','2'));`,
263+
{ chapter: Chapter.SOURCE_2, native: true }
264+
).toMatchInlineSnapshot('"4321"')
265+
})
266+
})
267+
268+
describe('length', () => {
269+
test('works with populated lists', () => {
270+
return expectResult(
271+
stripIndent`
272+
const xs = list(1,2,3,4);
273+
length(xs);
274+
`,
275+
{ chapter: Chapter.SOURCE_2, native: true }
276+
).toMatchInlineSnapshot('4')
277+
})
278+
279+
test('works with empty lists', () => {
280+
return expectResult(
281+
stripIndent`
282+
const xs = list();
283+
length(xs);
284+
`,
285+
{ chapter: Chapter.SOURCE_2, native: true }
286+
).toMatchInlineSnapshot('0')
287+
})
288+
})
289+
249290
// assoc removed from Source
250291
test.skip('assoc', () => {
251292
return expectResult(

src/stdlib/list.ts

+16-10
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function pair<H, T>(x: H, xs: T): Pair<H, T> {
2727

2828
// is_pair returns true iff arg is a two-element array
2929
// LOW-LEVEL FUNCTION, NOT SOURCE
30-
export function is_pair(x: any) {
30+
export function is_pair(x: any): x is Pair<any, any> {
3131
return array_test(x) && x.length === 2
3232
}
3333

@@ -55,7 +55,7 @@ export function tail(xs: any) {
5555

5656
// is_null returns true if arg is exactly null
5757
// LOW-LEVEL FUNCTION, NOT SOURCE
58-
export function is_null(xs: List) {
58+
export function is_null(xs: List): xs is null {
5959
return xs === null
6060
}
6161

@@ -71,7 +71,7 @@ export function list(...elements: any[]): List {
7171

7272
// recurses down the list and checks that it ends with the empty list null
7373
// LOW-LEVEL FUNCTION, NOT SOURCE
74-
export function is_list(xs: List) {
74+
export function is_list(xs: List): xs is List {
7575
while (is_pair(xs)) {
7676
xs = tail(xs)
7777
}
@@ -129,14 +129,20 @@ export function set_tail(xs: any, x: any) {
129129
}
130130
}
131131

132-
export function accumulate<T, U>(acc: (each: T, result: U) => any, init: U, xs: List): U {
133-
const recurser = (xs: List, result: U): U => {
134-
if (is_null(xs)) return result
135-
136-
return recurser(tail(xs), acc(head(xs), result))
132+
/**
133+
* Accumulate applies given operation op to elements of a list
134+
* in a right-to-left order, first apply op to the last element
135+
* and an initial element, resulting in r1, then to the second-last
136+
* element and r1, resulting in r2, etc, and finally to the first element
137+
* and r_n-1, where n is the length of the list. `accumulate(op,zero,list(1,2,3))`
138+
* results in `op(1, op(2, op(3, zero)))`
139+
*/
140+
export function accumulate<T, U>(op: (each: T, result: U) => U, initial: U, sequence: List): U {
141+
// Use CPS to prevent stack overflow
142+
function $accumulate(xs: typeof sequence, cont: (each: U) => U): U {
143+
return is_null(xs) ? cont(initial) : $accumulate(tail(xs), x => cont(op(head(xs), x)))
137144
}
138-
139-
return recurser(xs, init)
145+
return $accumulate(sequence, x => x)
140146
}
141147

142148
export function length(xs: List): number {

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"src/stdlib/**/*.js",
3232
"node_modules",
3333
"dist",
34-
"sicp_publish"
34+
"sicp_publish",
3535
],
3636
"types": [
3737
"typePatches",

0 commit comments

Comments
 (0)