Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating list-ops test generic types parameters #1477

Merged
merged 5 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 49 additions & 54 deletions exercises/practice/list-ops/.meta/proof.ci.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Null: Cons = {
const Null: Cons<undefined> = {
get value() {
return undefined
},
Expand All @@ -10,17 +10,17 @@ const Null: Cons = {
return this.value
},

push(item): Cons {
push<T>(item: T): Cons<T> {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return new Cons(item, this)
return new Cons(item, this) as Cons<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? Doesn't new Cons(item, this) already infer Const<T>? If not, shouldn't it be new Cons<T>(item, this)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case Cons would be of type Cons<T | undefined>, however because of how Null works the undefined part can be omitted, and since is of type Cons<T | undefined> using new Cons<T>(item, this) throws an error on TypeScript.

},
length() {
return 0
},
append(other: Cons): Cons {
append<T>(other: Cons<T>): Cons<T> {
return other
},
concat(): Cons {
concat(): Cons<undefined> {
return this
},
forEach(): void {
Expand All @@ -38,27 +38,27 @@ const Null: Cons = {
): TReturn {
return initial as TReturn
},
filter(): Cons {
filter(): Cons<undefined> {
return Null
},
reverse(): Cons {
reverse(): Cons<undefined> {
return this
},
map(): Cons {
return this
map<TReturn>(): Cons<TReturn> {
return this as Cons<TReturn>
},
}
class Cons {
class Cons<T> {
constructor(
public readonly value: unknown,
public next: Cons = Null
public readonly value: T,
public next: Cons<T> = Null as Cons<T>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably better to define it as:

public next: Cons<T> | Cons<undefined>

instead of needing the as Cons<T>

Copy link
Contributor Author

@Shoghy Shoghy Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can make the code a little bit messier with the types, and the only method where this is important is get, so that's why I made the return value of get be T | undefined

) {}

public get(i: number): unknown {
public get(i: number): T | undefined {
return i === 0 ? this.value : this.next.get(i - 1)
}

public push(item: unknown): this {
public push(item: T): this {
this.next = this.next.push(item)
return this
}
Expand All @@ -67,83 +67,78 @@ class Cons {
return 1 + this.next.length()
}

public append(other: Cons): Cons {
public append(other: Cons<T>): Cons<T> {
return other.foldl((result, item) => result.push(item), this)
}

public concat(others: Cons): Cons {
return others.foldl<Cons, Cons>(
(result, other) => result.append(other),
this
)
public concat(others: Cons<Cons<T>>): Cons<T> {
return others.foldl<Cons<T>>((result, other) => result.append(other), this)
}

public foldl<TValue = unknown>(
callback: (initial: TValue, value: TValue) => TValue
): TValue
public foldl<TValue = unknown, TReturn = unknown>(
callback: (initial: TReturn, value: TValue) => TReturn,
public foldl<TReturn = unknown>(
callback: (initial: TReturn, value: T) => TReturn
): TReturn
public foldl<TReturn = unknown>(
callback: (initial: TReturn, value: T) => TReturn,
initial: TReturn
): TReturn

public foldl<TValue = unknown, TReturn = unknown>(
callback: (initial: TReturn | undefined, value: TValue) => TReturn,
public foldl<TReturn = unknown>(
callback: (initial: TReturn | undefined, value: T) => TReturn,
initial?: TReturn
): TReturn {
return this.next.foldl<TValue, TReturn>(
callback,
callback(initial, this.value as TValue)
)
return this.next.foldl<TReturn>(callback, callback(initial, this.value))
}

public forEach(callback: (value: unknown) => void): void {
public forEach(callback: (value: T) => void): void {
this.foldl((_, item) => callback(item))
}

public foldr<TValue = unknown>(
callback: (initial: TValue, value: TValue) => TValue
): TValue
public foldr<TValue = unknown, TReturn = unknown>(
callback: (initial: TReturn, value: TValue) => TReturn,
public foldr<TReturn = unknown>(
callback: (initial: TReturn, value: T) => TReturn
): TReturn
public foldr<TReturn = unknown>(
callback: (initial: TReturn, value: T) => TReturn,
initial: TReturn
): TReturn

public foldr<TValue = unknown, TReturn = unknown>(
callback: (initial: TReturn, value: TValue) => TReturn,
public foldr<TReturn = unknown>(
callback: (initial: TReturn, value: T) => TReturn,
initial?: TReturn
): TReturn {
return callback(
this.next.foldr<TValue, TReturn>(callback, initial as TReturn),
this.value as TValue
this.next.foldr<TReturn>(
callback as (initial: TReturn, value: T | undefined) => TReturn,
initial as TReturn
),
this.value
)
}

public filter<TValue = unknown>(predicate: (value: TValue) => boolean): Cons {
return this.foldl<TValue, Cons>(
public filter(predicate: (value: T) => boolean): Cons<T> {
return this.foldl<Cons<T>>(
(result, item) => (predicate(item) && result.push(item)) || result,
Null
Null as Cons<T>
)
}

public map<TValue = unknown, TReturn = unknown>(
expression: (value: TValue) => TReturn
): Cons {
return this.foldl<TValue, Cons>(
public map<TReturn = unknown>(
expression: (value: T) => TReturn
): Cons<TReturn> {
return this.foldl(
(result, item) => result.push(expression(item)),
Null
Null as Cons<TReturn>
)
}

public reverse(): Cons {
public reverse(): Cons<T> {
return this.next.reverse().push(this.value)
}
}
export class List {
public static create(...values: unknown[]): Cons {
public static create<T>(...values: T[]): Cons<T> {
const [head, ...tail] = values

if (head === undefined) {
return Null
return Null as Cons<T>
}

return new Cons(head, List.create(...tail))
Expand Down
36 changes: 18 additions & 18 deletions exercises/practice/list-ops/list-ops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ describe('append entries to a list and return the new list', () => {
})

xit('list to empty list', () => {
const list1 = List.create()
const list1 = List.create<number>()
const list2 = List.create(1, 2, 3, 4)
expect(list1.append(list2)).toEqual(list2)
})

xit('empty list to list', () => {
const list1 = List.create(1, 2, 3, 4)
const list2 = List.create()
const list2 = List.create<number>()
expect(list1.append(list2)).toEqual(list1)
})

Expand All @@ -87,14 +87,14 @@ describe('append entries to a list and return the new list', () => {
describe('concat lists and lists of lists into new list', () => {
xit('empty list', () => {
const list1 = List.create()
const list2 = List.create()
const list2 = List.create<ReturnType<typeof List.create>>()
expect(list1.concat(list2)).toHaveValues()
})

xit('list of lists', () => {
const list1 = List.create(1, 2)
const list2 = List.create(3)
const list3 = List.create()
const list3 = List.create<number>()
const list4 = List.create(4, 5, 6)
const listOfLists = List.create(list2, list3, list4)
expect(list1.concat(listOfLists)).toHaveValues(1, 2, 3, 4, 5, 6)
Expand All @@ -103,13 +103,13 @@ describe('concat lists and lists of lists into new list', () => {

describe('filter list returning only values that satisfy the filter function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues()
const list1 = List.create<number>()
expect(list1.filter((el) => el % 2 === 1)).toHaveValues()
})

xit('non empty list', () => {
const list1 = List.create(1, 2, 3, 5)
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues(1, 3, 5)
expect(list1.filter((el) => el % 2 === 1)).toHaveValues(1, 3, 5)
})
})

Expand All @@ -127,47 +127,47 @@ describe('returns the length of a list', () => {

describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.map<number>((el) => ++el)).toHaveValues()
const list1 = List.create<number>()
expect(list1.map((el) => ++el)).toHaveValues()
})

xit('non-empty list', () => {
const list1 = List.create(1, 3, 5, 7)
expect(list1.map<number>((el) => ++el)).toHaveValues(2, 4, 6, 8)
expect(list1.map((el) => ++el)).toHaveValues(2, 4, 6, 8)
})
})

describe('folds (reduces) the given list from the left with a function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.foldl<number, number>((acc, el) => el * acc, 2)).toEqual(2)
const list1 = List.create<number>()
expect(list1.foldl((acc, el) => el * acc, 2)).toEqual(2)
})

xit('direction independent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldl<number, number>((acc, el) => acc + el, 5)).toEqual(15)
expect(list1.foldl((acc, el) => acc + el, 5)).toEqual(15)
})

xit('direction dependent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldl<number, number>((acc, el) => el / acc, 24)).toEqual(64)
expect(list1.foldl((acc, el) => el / acc, 24)).toEqual(64)
})
})

describe('folds (reduces) the given list from the right with a function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.foldr<number, number>((acc, el) => el * acc, 2)).toEqual(2)
const list1 = List.create<number>()
expect(list1.foldr((acc, el) => el * acc, 2)).toEqual(2)
})

xit('direction independent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldr<number, number>((acc, el) => acc + el, 5)).toEqual(15)
expect(list1.foldr((acc, el) => acc + el, 5)).toEqual(15)
})

xit('direction dependent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldr<number, number>((acc, el) => el / acc, 24)).toEqual(9)
expect(list1.foldr((acc, el) => el / acc, 24)).toEqual(9)
})
})

Expand Down