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

Fix signatures of JavaMap which can return undefined #67

Merged
merged 3 commits into from
Oct 10, 2016

Conversation

sharwell
Copy link
Member

📝 Strict null checks are currently disabled, but we're working to change that. See #26.

Copy link
Collaborator

@BurtHarris BurtHarris left a 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);
Copy link
Collaborator

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]>;
Copy link
Collaborator

@BurtHarris BurtHarris Oct 10, 2016

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);

Copy link
Collaborator

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.

Copy link
Member Author

@sharwell sharwell Oct 10, 2016

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.

Copy link
Collaborator

@BurtHarris BurtHarris Oct 10, 2016

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.

Copy link
Member Author

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);
Copy link
Collaborator

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);
Copy link
Collaborator

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);
Copy link
Collaborator

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]);
Copy link
Collaborator

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.

Copy link
Member Author

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]);
Copy link
Collaborator

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);

Copy link
Member Author

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 {
Copy link
Collaborator

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?

Copy link
Member Author

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 {
Copy link
Collaborator

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]);
Copy link
Collaborator

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.

@sharwell sharwell assigned BurtHarris and unassigned sharwell Oct 10, 2016
@BurtHarris BurtHarris merged commit c465469 into master Oct 10, 2016
@BurtHarris BurtHarris deleted the javamap-undefined branch October 10, 2016 22:12
@BurtHarris BurtHarris removed their assignment Oct 10, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants