tisonkun commented on issue #266:
URL: 
https://github.com/apache/datasketches-rust/issues/266#issuecomment-5510650977

   After reviewing the public APIs across the current sketch families, I lean 
toward **A: keep absence separate from caller errors**, with the return type 
chosen according to the actual failure modes of each query rather than 
mechanically applying `Result<Option<_>>` everywhere.
   
   The repository-wide pattern supports that distinction. Most existing 
`Result` APIs are justified: constructors validate configuration, deserializers 
validate an external representation, and unions/merges reject incompatible 
sketches. The main inconsistency is specifically in distribution queries: REQ 
currently treats an empty sketch as `InvalidArgument`, while T-Digest uses 
`Option` for emptiness but panics on invalid runtime parameters.
   
   I suggest the following policy:
   
   - `Option<T>` when the only non-success condition is that a valid sketch has 
no result yet.
   - `Result<T, Error>` when a result always exists for a non-empty sketch but 
the caller can supply an invalid argument.
   - `Result<Option<T>, Error>` when emptiness and invalid caller input are 
independent conditions.
   - `InvalidData` only at representation boundaries such as deserialization. 
Once `deserialize` returns `Ok`, queries should not surface serialized-data 
errors.
   - Panics only for internal invariant failures that cannot be produced 
through safe public APIs, not for ranks or split points that may reasonably 
come from runtime input.
   
   Applied concretely, this would likely mean:
   
   ```rust
   // No independently invalid query argument.
   fn rank(&self, item: &T, criteria: SearchCriteria) -> Option<f64>;
   
   // Empty is ordinary absence; an invalid rank is a caller error.
   fn quantile(&self, rank: f64, criteria: SearchCriteria)
       -> Result<Option<T>, Error>;
   
   fn cdf(&self, split_points: &[T], criteria: SearchCriteria)
       -> Result<Option<Vec<f64>>, Error>;
   ```
   
   T-Digest `rank` is slightly different from generic REQ rank because `NaN` is 
an invalid input, so its natural shape is also `Result<Option<f64>, Error>`. 
`min_value` and `max_value` should remain plain `Option`: that matches 
collection-style Rust APIs and there is no separate invalid argument to report.
   
   Arguments should be validated before checking whether the sketch is empty. 
Otherwise the same invalid rank would return `None` on an empty sketch and 
`Err(InvalidArgument)` after the first update, making validation depend on 
unrelated state. With `Result<Option<_>>`, the deterministic contract is:
   
   - invalid argument: `Err(InvalidArgument)`;
   - valid argument plus empty sketch: `Ok(None)`;
   - valid argument plus non-empty sketch: `Ok(Some(value))`.
   
   The main cost of A is nested handling. In practice, `?` removes the `Result` 
layer cleanly and the remaining `Option` is the state the caller genuinely 
needs to handle:
   
   ```rust
   let Some(value) = sketch.quantile(rank)? else {
       // no observations yet
       return Ok(...);
   };
   ```
   
   I would not recommend B as the primary model. `EmptySketch` would provide a 
single return layer and mirror the Java/C++ exception distinction, but it would 
make a normal, serializable, mergeable sketch state exceptional and enlarge the 
crate-wide error taxonomy mainly to avoid one level of nesting. We may still 
want separate kinds such as `IncompatibleSketch` or `CapacityExceeded` 
elsewhere, but that is independent of query absence.
   
   C keeps the current T-Digest API compact, but ranks and split points are 
commonly supplied by users, configuration, or query engines. Requiring every 
caller to duplicate prevalidation to avoid a process-level panic is a poor 
library boundary. D is attractive for workloads that reuse validated ranks, 
especially batch queries, but a public `NormalizedRank` plus generic 
split-point wrappers adds substantial API surface. It can remain a future 
ergonomic layer without being required to settle the base contract.
   
   For scope, I would first apply this policy to distribution-query families 
(T-Digest, REQ, and KLL) and to their sorted views. It should not be imposed 
mechanically on unrelated families: for example, an empty membership sketch can 
still answer membership queries, and an intersection that has not received its 
first input has its own existing state semantics.
   
   Since changing the existing REQ and T-Digest signatures is breaking, the 
cleanest path before 1.0 is to make the change once in a coordinated release. 
If an additive transition is needed first, T-Digest can gain checked `try_*` 
query methods while retaining the documented panic wrappers temporarily. I 
would avoid inventing asymmetric names such as `quantile_opt` for REQ solely to 
postpone the breaking change; that would leave the inconsistency in the 
long-term API.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to