多言語を習得する際の困り事
「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?
の逆。element
がenumerable
に含まれない場合に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