-
Notifications
You must be signed in to change notification settings - Fork 109
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
Fix signatures of JavaMap which can return undefined #67
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I would be OK as written, I suggest some changes.
@@ -248,7 +248,7 @@ export abstract class PredictionContext implements Equatable { | |||
return context; | |||
} | |||
|
|||
let existing: PredictionContext = visited.get(context); | |||
let existing: PredictionContext | undefined = visited.get(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it cleaner to simply drop the type specification for the variable existing
and allow type inference to drive. It seems particularly odd in juxtaposition with the following line comparing it against null
. I think it's a case of less is better, so my personal preference would be:
let existing = visited.get(context);
if (existing) {
return existing;
}
existing = contextCache.get(context);
if (existing) {
visited.put(context, existing);
return existing;
}
@@ -50,7 +50,7 @@ class MapKeyEqualityComparator<K, V> implements EqualityComparator<[K, V]> { | |||
} | |||
|
|||
export class Array2DHashMap<K, V> implements JavaMap<K, V> { | |||
private backingStore: Array2DHashSet<[K, V]>; | |||
private backingStore: Array2DHashSet<[K, V | undefined]>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This union type here seems more than a little hard to understand. We would never actually store a [key, undefined] in here, right?
I think it might be substantially clarifying to define a type alias type Bucket = [K, V];
then in places needed cast the temporary bucket wrapping a key only with as Bucket
. Perhaps even better: define an internal getBucket(key)
that centralizes all cases currently needing this substituting this.backingStore.get([key,value])
with this.getBucket(key)
;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P.S., I think we should consider having Array2DHashMap implement JavaScript's Map
as well as JavaMap
. As I remember, I couldn't do that for Set
vs JavaSet
due to a return-type conflict, but with Map
, I think it is possible. But it's probably best to do this as a separate, perhaps low-priority, change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to resort to as T
when there's an approach available that leverages full type inference. However, I also don't want the code to be confusing to readers. I think the solution here is a combination of a type alias and documentation explaining why the type alias includes | undefined
in the value type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than type Bucket = ...
, perhaps it would it help to define an non-exported class Bucket that hid the 'undefined'' in the constructor parameters would make it clearer. We could even combine that with functionality currently in the comparator, that only the key matters for equals/hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll like the approach I finally implemented. 😄
@@ -567,7 +567,7 @@ class ArrayPredictionContext extends PredictionContext { | |||
throw "Appending a tree suffix is not yet supported."; | |||
} | |||
|
|||
let result: PredictionContext = visited.get(context); | |||
let result: PredictionContext | undefined = visited.get(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above...
@@ -63,7 +63,7 @@ export class PredictionContextCache { | |||
return context; | |||
} | |||
|
|||
let result: PredictionContext = this.contexts.get(context); | |||
let result: PredictionContext | undefined = this.contexts.get(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above...
@@ -78,7 +78,7 @@ export class PredictionContextCache { | |||
} | |||
|
|||
let operands: PredictionContextCache.PredictionContextAndInt = new PredictionContextCache.PredictionContextAndInt(context, invokingState); | |||
let result: PredictionContext = this.childContexts.get(operands); | |||
let result: PredictionContext | undefined = this.childContexts.get(operands); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above...
@@ -61,7 +61,7 @@ export class Array2DHashMap<K, V> implements JavaMap<K, V> { | |||
} | |||
|
|||
containsKey(key: K): boolean { | |||
return this.backingStore.contains([key, null]); | |||
return this.backingStore.contains([key, undefined]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I almost prefer the version where this is null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 This is just omitted in the updated version
@@ -61,7 +61,7 @@ export class Array2DHashMap<K, V> implements JavaMap<K, V> { | |||
} | |||
|
|||
containsKey(key: K): boolean { | |||
return this.backingStore.contains([key, null]); | |||
return this.backingStore.contains([key, undefined]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I almost prefered the version where this is null. But if we follow the approach I suggested above, this might be
return this.backingStore.contains([key, null] as Bucket);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 This is just omitted in the updated version
put(key: K, value: V): V { | ||
let element: [K, V] = this.backingStore.get([key, value]); | ||
let result: V; | ||
put(key: K, value: V): V | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would this ever return undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➡️ put
returns the value previously associated with the specified key. If no value was already in the map for this key, the method returns undefined
.
put(key: K, value: V): V { | ||
let element: [K, V] = this.backingStore.get([key, value]); | ||
let result: V; | ||
put(key: K, value: V): V | undefined { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That this might return undefined
seems quite weird to me, but I guess that's what happens if the element key wasn't previously present in map.
We're doing something a bit wierd defining interface JavaMap, but then adopting it to return undefined rather than null as a concession to JavaScript.
let element: [K, V] = this.backingStore.get([key, value]); | ||
let result: V; | ||
putIfAbsent(key: K, value: V): V | undefined { | ||
let element: [K, V | undefined] = this.backingStore.get([key, value]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the one that convinced me it was confusing. While I understand the semantics implied by the comparitor ignores the value part of the bucket, that's non-local information that makes the behavior of this function hard to understand by examining only it's body.
📝 Strict null checks are currently disabled, but we're working to change that. See #26.