Project

General

Profile

Feature #14916

Proposal to add Array#===

Added by osyo (manga osyo) over 1 year ago. Updated over 1 year ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-dev:50593]

Description

概要

Array#=== を追加する提案になります。
基本的な動作は『Array#===== で比較する版』になります。

仕様

配列の各要素をそれぞれ順に === で比較し、全要素が true の場合に true を返す。そうでない場合は false を返す。

動作例

# 配列の各要素を #=== を使用して比較する
[ String, /\w/ ]          === [ "a", "c", 7 ]   # => false
[ String, /\w/, (1..10) ] === [ "a", "c", 7 ]   # => true
[ String, /\w/, (1..10) ] === [ "a", "!", 42 ]  # => false

真を返すケース

  • レシーバと引数が配列で同じサイズかつ、各要素を === で比較したときに全て true になる場合
  • レシーバと引数が同じオブジェクトの場合

偽を返すケース

  • レシーバと引数が配列で同じサイズかつ、各要素を === で比較したときに false がある場合
  • レシーバと引数の配列のサイズが異なる場合
  • 引数が配列以外の場合

ユースケース

引数の値によって処理を変える

可変長引数で引数を受け取り、そのまま case-when で値を精査する

def plus *args
  case args
  # 数値の場合
  when [Integer, Integer]
    args[0] + args[1]
  # 数字の場合
  when [/^\d+$/, /^\d+$/]
    args[0].to_i + args[1].to_i
  # それ以外はエラー
  else
    raise "Error"
  end
end

p plus 1, 2
# => 3
p plus "3", "4"
# => 7
p plus "homu", "mado"
# Error (RuntimeError)

FizzBuzz を用いた例

任意の処理の結果を複数回参照したい場合、配列でまとめて case-when で利用する

def fizzbuzz n
  _ = proc { true }
  case [n % 3, n % 5]
  # n % 3 === 0 && n % 5 === 0
  when [0, 0]
    "FizzBuzz"
  # n % 3 === 0
  when [0, _]
    "Fizz"
  # n % 5 === 0
  when [_, 0]
    "Buzz"
  else
    n
  end
end

p (1..20).map &method(:fizzbuzz)
# => [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz"]

関連しそうなチケット

以上、 Array#=== に関する提案になります。
挙動に関して疑問点や意見などございましたらコメント頂けると助かります。


Files

array_eqq.patch (3.06 KB) array_eqq.patch `===` で比較するだけ osyo (manga osyo), 07/17/2018 04:54 PM
array_eqq.patch (3.2 KB) array_eqq.patch `===` と `==` で比較する osyo (manga osyo), 08/08/2018 01:19 PM
array_eqq.patch (3.41 KB) array_eqq.patch Support `to_ary` osyo (manga osyo), 08/09/2018 02:22 AM

Related issues

Related to Ruby master - Feature #14869: Proposal to add Hash#===OpenActions
Related to Ruby master - Feature #14913: Extend case to match several values at onceClosedActions

History

#1

Updated by mrkn (Kenta Murata) over 1 year ago

#2

Updated by mrkn (Kenta Murata) over 1 year ago

  • Related to Feature #14913: Extend case to match several values at once added

Updated by baweaver (Brandon Weaver) over 1 year ago

I recently got permission to repurpose the Any gem, which gives us this:

require 'any'

case ['Foo', 25]
when [/^F/, Any] then true
else false
end
# => true

That should make this even more flexible, and eliminate the need for proc { true } explicitly.

https://github.com/baweaver/any

Updated by Eregon (Benoit Daloze) over 1 year ago

I think there is a potential incompatibility here, due to changing the behavior of #===.
The fact Module#=== doesn't return true for mod === mod exacerbates the issue.

For instance,

v = [String, 42]
case v
when [String, 42]
 p :ok
else
 :incompatible
end

Are there no existing use of case/when with an Array in when?

Updated by osyo (manga osyo) over 1 year ago

I think there is a potential incompatibility here, due to changing the behavior of #===.
The fact Module#=== doesn't return true for mod === mod exacerbates the issue.

こちらですが、互換性を壊さないように === だけではなくて == でも比較するようにし、いずれかの比較演算子の結果が真であれば true を返すように変更してみました。

String === String
# => false
[String] === [String]
# => true
[/aaa/] === [/aaa/]
# => true

v = [String, 42]
case v
when [String, 42]
 p :ok
else
 :incompatible
end
# => :ok

Updated by baweaver (Brandon Weaver) over 1 year ago

I've noticed that this will always return false if the other value is not an array:

(line 4011 of patch)

if (!RB_TYPE_P(ary2, T_ARRAY)) return Qfalse;

Have we considered potentially checking if the object responds to to_ary, and if so coercing it in the comparison?

This behavior is present in IPAddr, which coerces the right-hand value to an IPAddr before comparison.

I believe this would, while taking a minor speed hit, give much greater flexibility and duck-typing compatibility to this feature.

Updated by osyo (manga osyo) over 1 year ago

I believe this would, while taking a minor speed hit, give much greater flexibility and duck-typing compatibility to this feature.

OK, support call to_ary.

Updated by timriley (Tim Riley) over 1 year ago

Like I just mentioned in https://bugs.ruby-lang.org/issues/14869#note-13, would you consider changing this to run the explicit #to_a converter as well as (or instead of) the implicit #to_ary? This would make the matching even more flexible, since it could be using with objects that want to provide an interface for conversion into an array without necessarily pretending to "be" one (as is the case for #to_ary).

Also available in: Atom PDF