Page MenuHomePhabricator

Provide a mechanism by which Functions can opt-out of type conversion, as some edge cases need special handling (like non-simplified fractions in Wikidata quantities)
Open, Needs TriagePublicFeature

Description

Feature summary:
On Wikifunctions add two programming languages: python-raw and javascript-raw. These would be duplicates of the current available languages except for the behaviour of their conversions to and from code. The new conversions would usually mimic the raw object key structure as closely as possible.

Use case(s):
There are some functions which currently cannot work with code implementations, and must be constructed from compositions. This is because the lovely converters to code we have built deliberately obscure some of the technical details of the object they are being passed, and if that technical detail is important, then it would be good to have a method to access it.

A simple example is: https://www.wikifunctions.org/view/en/Z19733 [Numerator of unsimplified rational], and anything that relies on it (because higher up code implementations can't call Z19733 as a subroutine).

An example of where we want to return an unsimplified rational is here: https://www.wikifunctions.org/view/en/Z26982 [equal rational with numerator length].

You may ask: Didn't we agree that rationals are rationals and 1/2 == 2/4? So why would we ever need the unsimplified components? One example of why: When we built the Wikidata quantity type, we used rational keys as components, but in order to preserve the implicit precision of Wikidata quantities ("0.1000" is more precise than "0.1"), we do not simplify them when passing them to code. We make use of this nearly everywhere, even in the display functions. It also allows functions like https://www.wikifunctions.org/view/en/Z28509 [convert Wikidata quantity to compatible unit] to do very fancy stuff.

You may then ask: If the rationals in Wikidata quantities are already not simplified in code conversion, why do we need *-raw anyway? The rational component keys are often passed into other functions as rationals, where the code conversion occurs.

Benefits:

  • Code (especially JS) implementations are often a lot faster than compositions.
  • Code conversion is sometimes a little time consuming by itself.
  • Technical implementations would be more accessible. Often the current composition workarounds extend to multiple levels of composition and helper functions to ultimately call code anyway (once the technical object keys are extracted and passed down), which makes creating them extremely complicated (less than a handful of functioneers do this).
  • This solution would not interfere with existing implementations, but would just allow a parallel pathway when none is currently possible.
  • This solution will give us significant additional flexibility.
  • This solution may even help solve the problems we have with functions that return object-typed lists (e.g. https://phabricator.wikimedia.org/T370028 https://phabricator.wikimedia.org/T359500), where the back end code can't always guess what type it is receiving back, which prohibits code implementations. If code-raw implementations were possible, that would be a huge step forward.

Event Timeline

99of9 updated the task description. (Show Details)
99of9 updated the task description. (Show Details)
99of9 updated the task description. (Show Details)

I’m not sure about the specifics of this proposal, but I can certainly see opportunities that are currently blocked by default conversion. Custom conversion is generally avoidable but it would certainly be more convenient to be able to simply mark an implementation as requiring minimal conversion.

The recent enhancements to String from Type (Z22764) implemented by Z28969 are one example. Although currently limited to Type objects, the same approach could apply to other unconverted objects if inference of native forms like True and None were not required. (I’m planning to do just that, eventually).

Another example is String for function signature search (Z22973), which could be generalised to provide Search strings for compositions in implementations and test cases. I’m currently working on a lightweight parser for compact string representations, which is fine if you know your ZIDs, but still error-prone. To handle references specified using the user interface would require a composition but parsing the whole expression is better done in code, which then requires an orchestration function.

Jdforrester-WMF renamed this task from duplicate programming languages with raw/minimal conversion to code to Provide a mechanism by which Functions can opt-out of type conversion, as some edge cases need special handling (like non-simplified fractions in Wikidata quantities).Jan 21 2026, 7:00 PM
Jdforrester-WMF subscribed.

Open questions before we can consider solutions:

  • Does this operate at the whole-Function level, or is it a mode that the same Function can use and not use in some circumstances?
  • How does this apply to sub-Function calls (once re-entrancy is live)?
  • Does this only apply to inputs? To outputs? To only some inputs? Etc.
  • What test cases do we have beyond the Wikidata data model concern for non-simplified fractions?
  • Does this operate at the whole-Function level, or is it a mode that the same Function can use and not use in some circumstances?

According to the proposal, this would be done by creating two new Z61s, so the latter.

  • How does this apply to sub-Function calls (once re-entrancy is live)?

I guess you would be required to construct a ZObject to pass in (so if I wanted to use natural number addition as a sub-function, I would have to put {"Z1K1": "Z13518", "Z13518K1": {"Z1K1": "Z6", "Z6K1": "67"}} or something similar, if I had selected "python-raw" as my programming language)

  • Does this only apply to inputs? To outputs? To only some inputs? Etc.

To both inputs and outputs.

  • What test cases do we have beyond the Wikidata data model concern for non-simplified fractions

Basically any case where you want to operate on generic objects (or typed lists of them) without using a composition (and, even then it often breaks because of dependencies), take test Z17831, where you get "Could not serialize input Python object: 1" on the python implementation

  • Does this operate at the whole-Function level, or is it a mode that the same Function can use and not use in some circumstances?

According to the proposal, this would be done by creating two new Z61s, so the latter.

Yes, that is indeed what was proposed. However, my question is one that needs a decision first before we design a solution. Swift, simple answers that don't fully explore exactly what might get built in future, and how and why we want people to do this, are just creating technical and product debt.

  • Does this only apply to inputs? To outputs? To only some inputs? Etc.

Some of the other answers may depend on the decision here, so I'll answer it first. I suppose the ultra-flexible ideal would be to selectively apply to whichever inputs and outputs the implementation creator chooses. My specific proposal is for all inputs and all outputs, because I expect that will be easier to design, because this will only be needed in rare circumstances, because it does allow code to access everything, and because those who use it will be willing to go the extra mile on raw-processing other inputs/outputs if they need it for one. I'm open to seeing different designs before weighing in on whether I expect they would work.

  • Does this only apply to inputs? To outputs? To only some inputs? Etc.

Some of the other answers may depend on the decision here, so I'll answer it first. I suppose the ultra-flexible ideal would be to selectively apply to whichever inputs and outputs the implementation creator chooses. My specific proposal is for all inputs and all outputs, because I expect that will be easier to design, because this will only be needed in rare circumstances, because it does allow code to access everything, and because those who use it will be willing to go the extra mile on raw-processing other inputs/outputs if they need it for one. I'm open to seeing different designs before weighing in on whether I expect they would work.

Yes, the logical level of application is at the argument level for a particular implementation. This is what we are doing when we pass objects within a Quote object or Z1-typed list, but the current structure forces that distinction up to the function-argument level. See, for example, make Typed pair (Z30414), whose two implementations wrap different functions as a mechanism to pass unconverted objects into code implementations.

I’m not sure how we could apply an absence of conversion to a returned value, other than by returning a map or using ZObject(), ZReference() or ZPair(). Given that we now have those options, we might consider converting numbers by default, according to their native type, I suppose, but if the function’s return type is explicit, it should be respected.

I think contains (Z12696) is perhaps the best example to think of. Here, we don’t care whether or not conversion is applied to the list, so long as the target has the same treatment. There are quite a few similar cases to be found in this list. I can be more specific and also provide additional examples, if this would be helpful.

Without in any way leaping to propose a solution (not least because I worry about the performance implications), it would seem logical to suppose that calling back to the orchestrator for the required conversion on the required arguments would support all possible permutations. Having the option to leave any particular argument unconverted could then be seen as a possible performance optimisation.

I’m not sure how we could apply an absence of conversion to a returned value, other than by returning a map or using ZObject(), ZReference() or ZPair(). Given that we now have those options, we might consider converting numbers by default, according to their native type, I suppose, but if the function’s return type is explicit, it should be respected.

An example of where we need close-to-raw returned values is https://www.wikifunctions.org/view/en/Z26982 (equal rational with numerator length). The crucial requirement is that we separately return each of the keys of the object we are returning, and they don't get messed with during the conversion from code. Can we already do this with ZObject()?

I’m not sure how we could apply an absence of conversion to a returned value, other than by returning a map or using ZObject(), ZReference() or ZPair(). Given that we now have those options, we might consider converting numbers by default, according to their native type, I suppose, but if the function’s return type is explicit, it should be respected.

An example of where we need close-to-raw returned values is https://www.wikifunctions.org/view/en/Z26982 (equal rational with numerator length). The crucial requirement is that we separately return each of the keys of the object we are returning, and they don't get messed with during the conversion from code. Can we already do this with ZObject()?

I tried this:

	numerator = ZObject(
		{
			"Z1K1": "Z9",
			"Z9K1": "Z13518"
		},
		Z13518K1 = "400"
	)
	denominator = ZObject(
		{
			"Z1K1": "Z9",
			"Z9K1": "Z13518"
		},
		Z13518K1 = "60"
	)
	return ZObject(
		{
			"Z1K1": "Z9",
			"Z9K1": "Z19677"
		},
		Z19677K1 = ZReference("Z16660"),
		Z19677K2 = numerator,
		Z19677K3 = denominator
	)

Although this returns (positive) 400/60 in a Z1 context, it fails in conversion from code to a Rational number, because the converter is not tolerant of objects that do not require conversion. I guess that could be fixed in the converters themselves, which would make them double as pass-through validators.

I seem to recall @cmassaro suggesting callbacks to converter functions somewhere, which would tidy up construction of the Natural number objects, but the simplification logic embedded in the converters must be by-passed, as you know only too well. In this case, then, we would want to return the constructed ZObject and either avoid the converter altogether or ensure that it is idempotent for a valid Rational number object. Personally, I’m inclined to favour the latter option here, but I’m reluctant to widen this discussion into a debate on the future of validation. This leaves open the question of how converters ought to behave if presented with an explicitly constructed object that is structurally well-formed but invalid (for example, a zero numerator with a non-neutral Sign), since converters do not currently act as pass-through validators, but would not return such a result when converting from native values (because the Sign is derived).

Please see the Project Chat topic List typing still annoying, which references this ticket.