LoginSignup
9
4

More than 1 year has passed since last update.

[Elixir]RubyやRailsのメソッドは便利なものが多いので、それらをElixirでも使えるようにしたライブラリをリリースしました。

Last updated at Posted at 2022-01-16

多言語を習得する際の困り事

「Rubyだと配列からnullを排除する時 compactで出来たけど、Elixirだとどう書くんだろう。。
あれ、Enum.select って無いのか Enum.filterを使えばいいのね!」

Elixirを学習中に、上記のような ちょっと調べればわかるけれども、ちょっと調べないといけないような事が、ちょくちょく発生しました。

RubyやRailsのEnumerable系メソッドをElixirでも使えるようにしました!

RubyやRailsはその歴史の長さや、コミュニティの厚さにより、便利なmethodや直感的なaliasが多数定義されてます。
それらの資産を有効活用できないかと思い、REnumという、ElixirのmoduleをRuby/Rails風に拡張したライブラリをリリースしました。

なぜ、Enum(Enumerable)等に焦点を当てたかというと、Elixirの得意とするデータ変換にはEnum(Enumerable)等の拡張がもっとも有意に働くと思ったからです。

ElixirはRubyの文法の影響を大きく受けた言語でもあるので、Ruby => Elixirを学ぶ方はとても多いと感じております。そのような方の助けになりましたら幸いです。僕もそのうちの一人です。

機能紹介

RubyのEnumerableから移植してきたおすすめな関数達

compact(enumerable)

与えられたenumerableからnil以外のすべての要素を排除。

iex> [1, nil, 2, 3]
iex> |> REnum.compact()
[1, 2, 3]

each_slice(enumerable, amount, func)

amount毎に与えられたenumerableを分割し、各ブロックにfuncを適応してenumerableを返却。

iex> ["a", "b", "c", "d", "e"]
iex> |> REnum.each_slice(2, &IO.inspect(&1))
# ["a", "b"]
# ["c", "d"]
# ["e"]
["a", "b", "c", "d", "e"]

tally(enumerable)

等しい要素をkey, それらのカウントをvalueに持つマップを返却。

iex> ~w(a c d b c a)
iex> |> REnum.tally()
%{"a" => 2, "c" => 2, "d" => 1, "b" => 1}

grep(enumerable, func_or_pattern)

与えられた関数やパターンに一致する要素を選んで返却。

iex> ["foo", "bar", "car", "moo"]
iex> |> REnum.grep(~r/ar/)
["bar", "car"]

iex> 1..10
iex> |> REnum.grep(3..8)
[3, 4, 5, 6, 7, 8]

Alias

  • select(filterのalias)
  • detect(findのalias)
  • each_with_object(reduce/3のalias)
  • inject(reduceのalias)
  • include?(member?のalias)

RailsのActiveSupportから移植してきたおすすめな関数達

exclude?(enumerable, element)

Enum.member?の逆。elementenumerableに含まれない場合にtrueを返却。

iex> REnum.exclude?([2], 1)
true

iex> REnum.exclude?([2], 2)
false

without(enumerable, elements)

elements を除いて enumerable を返却。

iex> REnum.without(1..5, [1, 5])
[2, 3, 4]

iex> REnum.without(%{foo: 1, bar: 2, baz: 3}, [:bar])
%{foo: 1, baz: 3}

in_order_of(map_list, key, series)

元の map_list の要素のkeyに基づき、series で提供される順序に設定されたリストを返却。

iex> payments = [
...>   %Payment{dollars: 5, cents: 99},
...>   %Payment{dollars: 10, cents: 0},
...>   %Payment{dollars: 0, cents: 5}
...> ]
iex> REnum.in_order_of(payments, :cents, [0, 5])
[
  %Payment{cents: 0, dollars: 10},
  %Payment{cents: 5, dollars: 0}
]

pluck(map_list, keys_or_key)

map_list から、与えられたkeysのvalueを返却。

iex> payments = [
...>   %Payment{dollars: 5, cents: 99},
...>   %Payment{dollars: 10, cents: 0},
...>   %Payment{dollars: 0, cents: 5}
...> ]
iex> REnum.pluck(payments, [:dollars, :cents])
[[5, 99], [10, 0], [0, 5]]
iex> REnum.pluck(payments, :dollars)
[5, 10, 0]
iex> REnum.pluck([], :dollars)
[]

blank?(any)

any が空白、偽、空、または空白文字列である場合にtrueを返却。
例えば、nil, "", " ", [], {}, falseはtrueに該当する。

iex> REnum.Utils.blank?(%{})
true

iex> REnum.Utils.blank?([1])
false

iex> REnum.Utils.blank?("  ")
true

present?(any)blank?の偽を示す。

その他、個人的に解せなかった部分を解消した関数達

find_index_with_index(enumerable, func)

find_indexの際にfuncの第2引数としてcurrent_indexを渡せる用にした関数。

iex> REnum.find_index_with_index(1..3, fn el, index ->
...>   IO.inspect(index)
...>   el == 2
...> end)
# 0
# 1
1

list_and_not_keyword?(enumerable)

keyword listとlistを区別する関数。

iex> REnum.list_and_not_keyword?([1, 2, 3])
true

iex> REnum.list_and_not_keyword?([a: 1, b: 2])
false

map_and_not_range?(enumerable)

is_map(1..3) == trueなのが解せなくて作った関数。

iex> REnum.map_and_not_range?(%{})
true

iex> REnum.map_and_not_range?(1..3)
false

REnumは既存のEnum関数もすべて滞りなく使えます。

REnumはメタプログラミングにより、,ElixirのEnumや対応するEnumerable系のfunctionsを全て移譲しています。
なので、以下動作も問題なく使うことが出来ます。

iex> list = [1, 2, 3]
iex> REnum.find(list ,fn x -> rem(x, 2) == 1 end) == Enum.find(list ,fn x -> rem(x, 2) == 1 end)
true
iex> REnum.sort(list) == Enum.sort(list)
true

コード内のEnum.*REnum.*に置き換えても問題なく動くはずです。

終わりに

実装に関しては手が空いたタイミングで少しずつ拡充していこうと思っています。現在はEnumerable系の実装が終わり、List系の実装を開始しております。
もしよろしければスター等頂けましたら幸いです。コントリビュートも大歓迎です。

ロードマップは以下通り。

- [x] 0.1.0
  - REnum.Enumerable.Native
  - REnum.Enumerable.Ruby
  - REnum.Enumerable.Support
  - REnum.List.Native
  - REnum.Map.Native
  - REnum.Range.Native
  - REnum.Stream.Native
  - REnum.Utils
- [x] 0.2.0
  - REnum.Enumerable.ActiveSupport
- [ ] 0.3.0
  - REnum.List.Ruby
- [ ] 0.4.0
  - REnum.List.ActiveSupport
- [ ] 0.5.0
  - REnum.Map.Ruby
  - REnum.Map.ActiveSupport
- [ ] 0.6.0
  - REnum.Range.Ruby
  - REnum.Range.ActiveSupport
- [ ] 0.7.0
  - REnum.Stream.Ruby
  - REnum.Stream.ActiveSupport
9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4