Skip to content

Replace pass-through ... with dedicated options arguments #2790

Description

@krlmlr

In the 3.0.0 argument-migration program (#2757), ... becomes a pure
keyword-only separator: the generated ARG_HANDLE blocks own it to
recover legacy calls, and functions without recovery enforce
check_dots_empty(). Functions that forward their dots somewhere
else conflict with that design — and they already have the classic
dots problems today:

  • typos and stale arguments vanish silently — worse, three live
    functions forward dots into a callee that ignores them entirely, so
    everything passed there vanishes (see traps below);
  • no check_dots_empty() is possible, and the migration machinery
    cannot distinguish "argument for the callee" from "legacy positional
    argument to recover";
  • the set of accepted names is invisible in the signature and docs.

Proposal (per maintainer direction): each forwarding function gets
a dedicated options argument with its own logic — a validated named
list (constructor helpers where a family warrants them, in the spirit
of the existing options = arpack_defaults() pattern), unknown-name
errors instead of silent swallowing, and ... reserved for the
separator + recovery like everywhere else.

Affected functions (live API; complete inventory)

Layout family — forwards to a chosen layout function (21):
layout_nicely(), add_layout_(), layout_components(),
tk_reshape(), and the 17 deferred-spec wrappers
as_bipartite(), as_star(), as_tree(), in_circle(), nicely(),
on_grid(), on_sphere(), randomly(), with_dh(), with_drl(),
with_fr(), with_gem(), with_graphopt(), with_kk(),
with_lgl(), with_mds(), with_sugiyama().
Natural shape: layout_options(...) or mirroring the target's
formals in the wrapper.

Constructor-spec sugar — forwards into make_/sample_ (8):
chung_lu(), degseq(), tree(), directed_graph(),
undirected_graph(), from_literal(), from_data_frame(),
from_edgelist().

Isomorphism family — dots depend on method (6):
isomorphic(), subgraph_isomorphic(), count_isomorphisms(),
count_subgraph_isomorphisms(), isomorphisms(),
subgraph_isomorphisms().
Strongest case for "options with its own logic": the valid option set
is a function of the chosen method (vf2 / bliss / lad).

Foreign I/O — dots depend on format (2):
read_graph(), write_graph() (per-format readers/writers).
Same shape as the isomorphism family.

Statistics / conversion / misc (6):
component_distribution(), degree_distribution(),
fit_power_law() (only its "R.mle" branch forwards),
scan_stat() (→ local_scan()), print_all() (→ print options),
as.matrix.igraph() (→ as_adjacency_matrix()/as_edgelist()).

Plotting S3 (8): plot.igraph(), rglplot.igraph(), plot.sir(),
plot.communities(), plot.cohesiveBlocks(), plot_hierarchy(),
plot_dendrogram.communities(), plot_dendrogram.igraphHRG().
S3 methods must keep ... for the generic contract; the question is
whether they additionally gain an options/params argument so the
graphical parameters stop hiding in dots.

30 deprecated shims also forward dots; per policy they are never
touched and simply follow (or outlive) their targets.

Traps found while inventorying (bugs today)

  • chung_lu() forwards ... into sample_chung_lu(), which has
    ... before its own keyword args and ignores it — anything a
    user passes there vanishes silently.
  • minimum.spanning.tree()mst() and
    graph.cohesion()cohesion.igraph() swallow dots the same way.
  • make_graph()'s numeric branch does
    do.call(old_graph, list(edges, ...)), so unnamed extra dots can
    positionally bind the internal n/directed parameters.

Explicitly out of scope (dots are the payload, not options)

  • Variadic-data functions where dots are the data (30):
    make_graph(), graph_from_literal(), edge()/vertex()/path(),
    add_edges()/add_vertices() (attributes), set_vertex_attrs(),
    disjoint_union(), the union()/intersection()/difference()
    families (dots-only dispatch), layout_() modifiers,
    igraph_options(), with_vertex_()/with_edge_()/with_graph_()
    (NSE), c()/[ methods on vs/es.
  • User-callback forwarders where dots belong to FUN:
    local_scan(), head_print(), indent_print().
  • The 17 functions already enforcing check_dots_empty(), and pure
    separators — already correct.

Questions for maintainers

  • One generic options = list() everywhere, or per-family
    constructor helpers (layout_options(), per-method isomorphism
    options, per-format I/O options) with validation?
  • For the deferred layout sugar (with_fr() etc.): options list,
    or mirror the target function's formals outright?
  • Plotting: keep dots-only (S3 reality), or add a params/
    options argument alongside the contractual ...?
  • Transition: recover dots-passed options via the ARG_HANDLE
    machinery with a soft deprecation ("passing layout parameters
    through ... is deprecated, use options ="), so old code
    keeps working through 3.x?
  • Fix the three silent-swallowing traps above ahead of the
    redesign (they are user-visible bugs regardless)?

Related: #2757 (program), #2780 (@param ... docs), #2788 (renames),
#2789 (defaults). No existing issue covered this space, hence no
Closes lines.

Metadata

Metadata

Assignees

No one assigned

    Labels

    argument consistencyissues related to argument naming/defaultslifecycleDeprecating old APIs

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions