Directional language

Arabic runs right to left. The data should say so.

rdf12-directional-language.py · output · proof · check · try it in the playground


The question

A website has a welcome label in Arabic, مرحبا, and in English, Welcome. Arabic is written right to left, English left to right. A screen that does not know the direction can garble the text, especially when it is mixed with numbers or punctuation.

RDF is a standard way to publish data on the web as simple statements: this thing has this property with this value. Version 1.2 of RDF lets a piece of text carry both its language and its base direction, written "مرحبا"@ar--rtl (“Arabic, right to left”).

When a rule turns every label into a display label, do the language and the direction survive?


What we tell eyekind

ex = 'https://example.org/'

fact(
  rdf(iri(ex + 'welcome'), iri(ex + 'label'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph'),
  rdf(iri(ex + 'welcome'), iri(ex + 'label'), literal('Welcome', lang('en', 'ltr')), 'default_graph'),
)

# Every label is also a display label, with its language and direction.
implies(rdf(S, iri(ex + 'label'), Label, G), result_rdf(S, iri(ex + 'displayLabel'), Label, G))
query(result_rdf(S, P, O, G))

An eyekind program is Python, so ex = ... simply names a piece of text, and ex + 'welcome' glues two pieces together into https://example.org/welcome.

The data is written as ordinary terms:

One fact(...) holds two statements here, so they become facts 1 and 2: the Arabic label and the English label.

The implies(premise, conclusion) line is a rule, number 3: whenever the premise holds, eyekind concludes the conclusion. Capitalized names (S, Label, G) are variables, blanks that stand for whatever fits. The rule says: whatever S has as a label, it has as a display label too, in the same graph. Label stands for the whole text value, language and direction included, so the rule passes it on untouched. result_rdf marks the statements that are results. The query asks for all of them.


What eyekind concludes

// Answers: each instance of a query() that holds.
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph')
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('Welcome', lang('en', 'ltr')), 'default_graph')

Two display labels for the welcome page. The Arabic one still says 'ar', 'rtl'; the English one still says 'en', 'ltr'. A screen can lay out each text the right way.


The proof, line by line

eyekind --proof writes down how it reached each answer. This is the complete proof:

// Answers: each instance of a query() that holds.
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph')
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('Welcome', lang('en', 'ltr')), 'default_graph')

// Clauses: the program clauses the steps cite, by clause number.
clause(1, fact(rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph')))
clause(2, fact(rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('Welcome', lang('en', 'ltr')), 'default_graph')))
clause(3, implies(rdf(S, iri('https://example.org/label'), Label, G), result_rdf(S, iri('https://example.org/displayLabel'), Label, G)))

// Steps: each goal, what justifies it (a clause, a builtin, a control, or an
// absence or collection taken on trust), the bindings and the goals it uses.
step(result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph'), clause(3), {S: iri('https://example.org/welcome'), Label: literal('مرحبا', lang('ar', 'rtl')), G: 'default_graph'}, [rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph')])
step(rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph'), clause(1), {}, [])
step(result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('Welcome', lang('en', 'ltr')), 'default_graph'), clause(3), {S: iri('https://example.org/welcome'), Label: literal('Welcome', lang('en', 'ltr')), G: 'default_graph'}, [rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('Welcome', lang('en', 'ltr')), 'default_graph')])
step(rdf(iri('https://example.org/welcome'), iri('https://example.org/label'), literal('Welcome', lang('en', 'ltr')), 'default_graph'), clause(2), {}, [])

It has three parts:

Read the steps as sentences:

  1. The welcome page has the Arabic display label, right to left: by rule 3, with S = the welcome page, Label = the whole Arabic text value and G = the default graph, because the welcome page has that label.
  2. The welcome page has the Arabic label: fact 1; it uses nothing.
  3. The welcome page has the English display label, left to right: by rule 3, with Label = the whole English text value, because the welcome page has that label.
  4. The welcome page has the English label: fact 2.

The value of Label in steps 1 and 3 is the whole literal, direction and all. That is why the direction cannot be lost: the rule never takes the value apart.


Checked: what the report says

A proof is only worth something if someone else can check it. eyekind -c generates the proof and checks it against the program; --check-proof checks a saved proof. The checker never reasons by itself: it only confirms each step against the program. This is the complete report:

// Checked answers: these follow from the input program.
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('مرحبا', lang('ar', 'rtl')), 'default_graph')
result_rdf(iri('https://example.org/welcome'), iri('https://example.org/displayLabel'), literal('Welcome', lang('en', 'ltr')), 'default_graph')

// C1-C8: resolution, acyclicity, justification, coverage,
// re-decision, boundary consistency, relevance and witness integrity.
condition('C1', 'resolution', 'ok', 4)
condition('C2', 'well_founded', 'ok', 4)
condition('C3', 'justification', 'ok', 4)
condition('C4', 'coverage', 'ok', 4)
condition('C5', 're_decision', 'ok', 0)
condition('C6', 'boundary_consistency', 'ok', 0)
condition('C7', 'relevance', 'ok', 6)
condition('C8', 'witness_integrity', 'ok', 0)

// Proof statistics: recorded steps and how they were checked.
steps(4)
verified(4)
recomputed(0)
composed(0)
trusted(0)
claims(2)

// This verdict applies to the claims above; it does not establish completeness.
verdict('checked')

Each condition line names a check, says 'ok', and counts what it looked at:

The statistics repeat this: 4 steps, all 4 verified against the program, none taken on trust, for 2 claims. The verdict is checked: both display labels, with their directions, follow from the program. As the last comment says, it confirms these answers; it does not say there are no others.


Try it

eyekind examples/rdf12-directional-language.py              # the results as quads
eyekind --proof examples/rdf12-directional-language.py      # with their proof

The converters of rdf-interchange turn eyekind’s quads into RDF, such as Turtle, and RDF into quads. Or open it in the playground.

Add a Hebrew label to the facts, rdf(iri(ex + 'welcome'), iri(ex + 'label'), literal('ברוכים הבאים', lang('he', 'rtl')), 'default_graph'), and run again: a third display label appears, still marked 'he', 'rtl'.


Takeaway

A rule that passes a value on whole cannot lose part of it: the direction arrives on the screen as it left the data.