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

Nuance "plain objects (JSON) cannot be used." #104

Closed
RubenVerborgh opened this issue Mar 8, 2017 · 20 comments
Closed

Nuance "plain objects (JSON) cannot be used." #104

RubenVerborgh opened this issue Mar 8, 2017 · 20 comments

Comments

@RubenVerborgh
Copy link
Member

RubenVerborgh commented Mar 8, 2017

The statement

Given the necessity of methods, plain objects (JSON) cannot be used.

if confusing; we probably want something more correct.

@elf-pavlik
Copy link
Member

This seems relevant to https://github.com/rdfjs/representation-task-force/issues/79 (forwarding DataFactory). One could pass factory that creates objects without methods?

@RubenVerborgh
Copy link
Member Author

That would be a spec-incompatible factory, but yes, could be so for simplicity.

@elf-pavlik
Copy link
Member

This issue in rdflib.js by @fletcher91 seems relevant: linkeddata/rdflib.js#219 (Functional-style datamodel & performance)

@vhf
Copy link
Member

vhf commented Nov 29, 2018

While we are at it, I'd suggest removing (JSON) because JSON only exists as a string, not as JavaScript object(s). (A JSON object is a string.)

I understand that what is meant by "JSON objects" is something like objects created using the literal syntax or Object.create(null) but I still find the phrase plain objects (JSON) confusing.

@elf-pavlik
Copy link
Member

If we want to ship at least the Data interfaces spec, do we still need to address something in this issue?

This issue in rdflib.js by @fletcher91 seems relevant: linkeddata/rdflib.js#219 (Functional-style datamodel & performance)

@RubenVerborgh I think currently you might have the best understanding of rdflib issues, I see that issue mentioned above resulted in PR which claims: linkeddata/rdflib.js#272 (comment)

So, I've created a turtle parsing benchmark, and the results show about a 40% decrease in memory usage (~55% after garbage collection), and an 8% reduction in processing time.

Looking at the difference before and after the gc call, there seems to be some additional space for reducing memory usage.

@RubenVerborgh
Copy link
Member Author

In fact, any library should be able to convert plain JSON objects into objects with methods.

Relevant: #137

@RubenVerborgh
Copy link
Member Author

This issue in rdflib.js by @fletcher91 seems relevant: linkeddata/rdflib.js#219 (Functional-style datamodel & performance)

@RubenVerborgh I think currently you might have the best understanding of rdflib issues, I see that issue mentioned above resulted in PR which claims: linkeddata/rdflib.js#272 (comment)

I've scanned this, but I don't think they are relevant.

@rescribet
Copy link

To clear up any possible confusion, the changes in linkeddata/rdflib.js#272 don't implement the issue brought up in linkeddata/rdflib.js#219

Rather than rewriting rdflib.js to a functional model, it merely ensures any Term has at most and only one instance. So calling a factory with the same argument multiple times always returns the same instance, as does calling the constructor with the same argument (DataFactory.namedNode('http://ex.com') === DataFactory.namedNode('http://ex.com') === new NamedNode('http://ex.com') // true)

On a second note, consider the following;

a = {
  termType: 'NamedNode',
  value: 'http://example.com',
  equals(other) {
    return this.termType == other.termType && this.value === other.value
  }
}

b = {
  termType: 'NamedNode',
  value: 'http://example.com',
  equals(other) {
    return this.termType == other.termType && this.value === other.value
  }
}

a.equals(b) // true

Both a and b were created via literal syntax and inherit from Object, but also have methods. Given the spec lacks a definition of 'plain', are these not plain objects?

@bergos
Copy link
Member

bergos commented Jan 23, 2019

Maybe we just remove:

Given the necessity of methods, plain objects (JSON) cannot be used.

as it sounds like the spec forbids any usage of JSON in the context of the RDFJS Data Model

and

Should allow "upgrading" a plain object into a fully functional triple

cause we don't have a standard way to do it and based on the experience using the current Data Factory, I don't see the requirement. Of course any library can implement it.

@elf-pavlik
Copy link
Member

I see that https://github.com/rdfjs/data-model/ also optimizes memory use by storing all the redundant values (like termType) on prototype which one can't do with directly serializable objects.

@blake-regalia
Copy link
Contributor

As @vhf mentioned, let's try to avoid using the term "JSON" here since that is a string interchange format and what we are talking about here (perhaps for lack of a better phrase) are 'plain objects'.

In fact, in graphy, such plain objects are allowed in certain places since they are technically superclasses of Term/Quad and similarly can be exported using the .isolate method which simply means generating a 'plain object' representation that is devoid of references to other objects.

@earthlyreason
Copy link

Nothing in the spec necessitates methods. (Or namespaces, for that matter.)

“Equality” and “conversion” are cited. The spec never mentions conversion. As for equality:

function equals(one, other) {
  if (one && other && one.termType === other.termType) {
    switch (one.termType) {
      case "DefaultGraph":
        return true;

      case "NamedNode":
      case "BlankNode":
      case "Variable":
        return one.value === other.value;

      case "Literal":
        return (
          one.value === other.value &&
          one.language === other.language &&
          equals(one.datatype, other.datatype)
        );

      default:
        return (
          equals(one.subject, other.subject) &&
          equals(one.predicate, other.predicate) &&
          equals(one.object, other.object) &&
          equals(one.graph, other.graph)
        );
    }
  }

  return false;
}

Those are the equality semantics of the spec. Forever. RDF is twenty years old and it's not getting any new primitives (so no “expression problem”).

With that, a consumer can process values:

import { equals } from "some-rdf-api-implementation"

const select = (triples, subject) =>
  triples.filter(_ => equals(_.subject, subject));

More importantly, a producer can construct valid resources with no runtime dependencies:

const jaws_of_victory = () => ({
  subject: { termType: 'NamedNode', value: 'http://rdf.js.org/' },
  predicate: { termType: 'NamedNode', value: 'http://snatches.org' }
  object: 'http://defeat.org',
  graph: { termType: 'DefaultGraph', value: "" }
};

Helper functions are trivial to write, and they do not need to be conformant. Conformant factory functions can confer implementation-specific benefits (e.g. @fletcher91 can optimize space by memoizing or interning)—but any piece of data can be processed. If you don't trust incoming values, you can normalize them (akin to #137), but you don't have to.

That is the “minimal necessary interface.”

Mandating the use of methods amounts to specifying an implementation.

As things stand, producers effectively MUST import or implement the spec, just so that the equality methods can be schlepped around with every single value, as required.

Why?

The original RDF API spec, which failed, also took this “OOP” approach, perhaps because that's what is codified by the industrial-strength “WebIDL” meta-spec that it follows. The stated goal of WebIDL is merely to support interop on the web, and nowhere does it motivate the exclusive use of classes as top-level constructs. Clearly, though, it targets the standardization of built-ins such as XMLDOM, SVG, and other vendor-provided API's that will live in the global namespace. As such, it never mentions modules, which are the province of specs like this one, which will be fulfilled (or not) by userland implementations that can export whatever they want.

Flexibility is lost, and nothing is gained by the use of methods and namespaces. Their use is not motivated by the spec, and the design notes, which consider alternate approaches, appear inconclusive.

The wish to support “plain objects” is not just about deserialization (as asserted in #143). Consumers of protocols need facilities, but many producers don't. The fact that you can reconstitute a conformant value on the other end of a socket just illustrates that the method wasn't carrying anything essential and is only needed by the receiver. The same is true for interop within runtimes.

Yes, JSON-LD was designed as a transport format. But the insistence on support for passive conformance by data producers (what Sporny called “zero edits”) has been critical to its success, and it is a general design principle.

Please forgive my lack of “nuance.” I am a semantic web convert and I want RDF to succeed. I am just giving my perspective as an observer.

@RubenVerborgh
Copy link
Member Author

The RDF/JS representation task force is about finding a common way to deal with the RDF model with a JavaScript-idiomatic API.

The RDF API spec was not JavaScript-idiomatic.

Floating methods are not JavaScript-idiomatic.

JSON-LD is a JavaScript-idiomatic data format.

The objects we propose in the current RDF/JS spec are a JavaScript-idiomatic API. This is the core of everything. I welcome any objections to that point, but I have not seen any.

@rescribet
Copy link

rescribet commented Jan 28, 2019

With regards to the more functional approach (be it Term.equals(a, b) or bring-your-own equals(a, b)), new syntax is in development to facilitate that. That doesn't make it idiomatic, but it seems to be gaining more traction. In any case, if "plain objects" are allowed, all functional-style libraries will be able to consume the objects regardless of the methods available.

The objects we propose in the current RDF/JS spec are a JavaScript-idiomatic API

Isn't the ability to use equality operators, plain literals primitives, and arrays idiomatic as well? (Cheeky thought; five = Number(5) is extendable with properties and methods). So it already seems somewhat off a full idiomatic API.

finding a common way to deal with the RDF model with a JavaScript-idiomatic API

I feel there is an argument for emphasising 'common' over 'javascript-idiomatic', since excluding a sizeable part of the JS community and functional languages which compile to JS might put a wedge in-between the already niche JS-based RDF community.

Extracting the method interfaces for both Term.equals(x, y) and x.equals(y) into their own spec extensions would accommodate both worlds, especially if some well-known global can be defined which libraries can use to mint objects, so the application writer can choose their object provider be it functional or OOP whichever needed.

@RubenVerborgh
Copy link
Member Author

I feel there is an argument for emphasising 'common' over 'javascript-idiomatic',

There surely is, but that was never the goal of RDF/JS (note the 'JS' 😉).

All we want is an API that looks like all other JavaScript APIs.

@elf-pavlik
Copy link
Member

elf-pavlik commented Jan 28, 2019

In #142 (comment) I suggested not to 'bless' any specific equals() and leave it as custom convenience method offered by implementations. This way subset of libraries which try to only depend on core RDF/JS data model may need bring equality function that meets their expectations.

@bergos suggested on chat that we try to gather all the arguments in one place so that we can make well informed decision all together: https://gitter.im/rdfjs/Representation-Task-Force?at=5c4ee99ef04ef006449c86b5

@rescribet
Copy link

@RubenVerborgh Well, "common" within the scope of JS of course, but it seems we can have our cake and eat it too. The plain object definition would be a strict subset of either FP and OOP extensions. So it'd seem arbitrary to limit a low-level spec to exclude projects/people, especially since JS' main building block is Function ;)

@RubenVerborgh
Copy link
Member Author

In #142 (comment) I suggested not to 'bless' any specific equals() and leave it as custom convenience method offered by implementations.

Defining equality is core to interoperability, so that would be a -1 from me.

The plain object definition would be a strict subset of either FP and OOP extensions.

But who's asking to restrict ourselves to such a subset? Which project or use case tangibly benefits from such a subset?
All we set out to do with the representation task force was an idiomatic JavaScript API.

@RubenVerborgh
Copy link
Member Author

Look, the original issue here about plain objects has been resolved in #147, so as the creator of this issue, I will close it.

By no means do I want to stop the discussion that @fletcher91 and @elf-pavlik want to have, but that is really a different issue. So if you want to continue, by all means, but please start a new issue specifically about equals.

@RubenVerborgh
Copy link
Member Author

I've done so myself: #150

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants