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.

Activity

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

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