Skip to content

proposal: exp/slices: change slices.BinarySearchFunc E, T ordering in the closure #59260

Closed
@twmb

Description

@twmb

Today we have:

func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool)

The proposal is to switch it to:

func BinarySearchFunc[E, T any](x []E, target T, cmp func(T, E) int) (int, bool)

Why?

tldr: the current ordering feels like sort.Search, which is awkward because "you have to think in terms of greater-than rather than less-than"

The original BinarySearchFunc was:

func BinarySearchFunc[E any](x []E, target E, cmp func(E, E) int) (int, bool)

Meaning that if I wanted to implement an inline closure, it was not obvious which argument my target would be. I would most likely implement this as general less function: given left and right, return whether left is less than right.

#57348 proposed allowing the target to be a different type. @rsc asked whether E, T is the standard order here, and @aclements mentioned that it is, given Python's bisect and Java's binarySearch. I think the @aclements reply was actually talking about the first two arguments to the binary search function, not the closure's arguments: the first two arguments are always array, then target.

No other language that has a binary search function uses a comparison function similar to BinarySearchFunc's closure: no other language explicitly has the target being the first argument or the second argument. C++'s std::binary_search existed before lambda expressions and the Compare function never calls out argument ordering (in fact, it flips: here then here). C# is similar to C++, C is just completely type blind through void *. Java's Arrays.binarySearch existed since 1.6, before lambdas were introduced in 1.8, and the input Comparator never mentions argument ordering. Rust and Ruby do not even have something similar to slices.BinarySearchFunc -- they have something similar to sort.Find.

In #50340, @rogpeppe mentions that sort.Search is awkward and one of the reasons is that "you have to think in terms of greater-than rather than less-than". This is the basis of the proposal.

When the comparison function uses identical types a person never needs to consider the target in the comparison function. Now that the comparison function supports a different type, if you use a different type, you are thinking backwards. If a person wants to implement the comparator function against a target whose type T is different from the input slice element type E, they now have to think in terms of greater-than rather than less-than. The new comparison function feels like sort.Search.

Example of how I want to write slices.BinarySearchFunc using sort.Find, following with how I always actually do write a buggy comparison function and then spend too much time thinking about conditionals: https://go.dev/play/p/RLsTqohcXcA.


Also as a last unimportant aside that might be in a later proposal, I'd actually prefer:

  • making it much clearer why I'd use sort.Find vs. slices.BinarySearchFunc
  • revert x/exp/slices: Allow different types for haystack/needle in BinarySearchFunc #57348 because these multi-type use cases seems better suited for sort.Find
  • mention sort.Find in the docs of slices.BinarySearchFunc (mostly because I completely forgot about sort.Find and personally prefer the closure way of doing things)
  • switch BinarySearchFunc to be similar to sort.Find with an index and no target -- the current form seems tailored for primitive types, not large structs

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions