Skip to content

Add the LINQ operators Query.jl is missing - #360

Open
davidanthoff wants to merge 1 commit into
mainfrom
add-missing-linq-operators
Open

davidanthoff wants to merge 1 commit into
mainfrom
add-missing-linq-operators

Conversation

@davidanthoff

Copy link
Copy Markdown
Member

Third of four PRs. Adds the user-facing macros and documentation for the 38 operators in QueryOperators.jl#56, closing the gap between Query.jl's operator set and System.Linq.Enumerable as it stands at .NET 11.

CI will stay red until QueryOperators 1.2.0 is registered. The [compat] bound is an ordinary QueryOperators = "1.2"; no feature guards. Locally the full suite passes: 34 test items (18 new) plus 20 new doctests.

What lands

@left_join @right_join @full_join · @concat @union @union_by @except @except_by @intersect @intersect_by · @take_while @drop_while @take_last @drop_last · @order @order_descending @reverse @shuffle @index · @append @prepend @zip · @count_by @aggregate_by @chunk · @of_type @cast · @min_by @max_by @any @all @contains @sequence_equal @aggregate @first @last @single @element_at

Each has both the direct and piped form and a documentation section in standalonequerycommands.md with a runnable example. All 20 new doctests pass.

Four macros that needed a decision

The piped and direct forms are normally told apart by arity — @take(2) versus @take(df, 2). Four operators broke that, and rather than guess from what an argument looks like, the discriminator is keyword syntax, which is syntactically unambiguous:

  • @shuffle takes its generator as rng=: df |> @shuffle(rng=MersenneTwister(42)). A lone positional argument is then always the source.
  • @aggregate takes its seed as seed=: df |> @aggregate(f, seed=0), @aggregate(df, f, seed=0).
  • @zip has no two-argument piped form with a result selector — it would be indistinguishable from @zip(source, other). df |> @zip(other) |> @map(...) expresses the same thing, so nothing is lost.
  • @any, @first, @last, @single mirror the existing @count: no piped form taking a predicate, because a single argument is the source. Write df |> @filter(...) |> @any().

Other notes

Outer joins fill with DataValue, never missing, both before and after the sink. A test asserts the collected rows carry DataValue and that ismissing is false for all of them.

@except_by and @intersect_by deviate from .NET, as flagged in the QueryOperators PR: .NET takes a bare sequence of keys there, while UnionBy takes elements. All three take elements here and apply the selector to both sequences. Documented explicitly in the @union_by section.

@count_by names its key columns the way @summarize does — scalar key becomes key, named tuple key splats — and a test asserts @count_by(_.k) equals @groupby(_.k) |> @map({key=key(_), count=length(_)}).

No new @from clauses. LINQ query-expression syntax has no keywords for these operators, so query_translation.jl is untouched, as is the existing @left_outer_join sugar.

Conflict warning

This touches the export list in src/Query.jl and appends to docs/src/standalonequerycommands.md — the same two files as the pending @summarize PR. Whichever merges second needs a trivial rebase. Happy to rebase this one on top if you merge @summarize first.

🤖 Generated with Claude Code

Adds the user-facing macros for the 38 operators introduced in
QueryOperators 1.2, each in both the direct and piped form, plus a
documentation section with a runnable example for every one.

Most follow the existing templates exactly. Four could not, because the
piped and direct forms would have had the same arity:

- @Shuffle takes its generator as `rng=`, so a lone positional argument is
  unambiguously the source.
- @aggregate takes its seed as `seed=`, for the same reason.
- @zip has no two-argument piped form with a result selector; it would be
  indistinguishable from the direct form without one, and
  `|> @zip(other) |> @Map(...)` says the same thing.
- @any, @FIRST, @last and @single mirror @count: no piped form taking a
  predicate, since a single argument is the source.

Keyword syntax is used as the discriminator rather than inspecting what an
argument looks like, so the choice is syntactic rather than a heuristic.

Requires QueryOperators 1.2, which is not registered yet, so CI here stays
red until it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant