Permalink
Browse files

Add Enum.nth, change IEx code to rely on it

  • Loading branch information...
1 parent 0252cd8 commit ae4bbed5d04650c077729397025c7c8b258e1a98 @josevalim josevalim committed Jul 11, 2012
View
38 lib/elixir/lib/enum.ex
@@ -383,7 +383,7 @@ defmodule Enum do
Enum.find_index [2,4,6], fn(x) -> rem(x, 2) == 1 end
#=> nil
- Enum.find_value [2,3,4], fn(x) -> rem(x, 2) == 1 end
+ Enum.find_index [2,3,4], fn(x) -> rem(x, 2) == 1 end
#=> 2
"""
@@ -548,6 +548,32 @@ defmodule Enum do
end
@doc """
+ Finds the element at the nth index. Returns nil in case
+ the given index is outside the range of the collection.
+
+ Expects an ordered collection.
+
+ ## Examples
+
+ Enum.nth [2,4,6], 1 #=> 2
+ Enum.nth [2,4,6], 3 #=> 6
+ Enum.nth [2,4,6], 5 #=> nil
+
+ """
+ def nth(collection, n) when is_list(collection) and n > 0 do
+ do_nth(collection, n)
@yrashk
yrashk Jul 11, 2012

why not delegate to :lists.nth?

@josevalim
josevalim Jul 11, 2012

Because we return nil if index > list(length)

@yrashk
yrashk Jul 11, 2012

so intentionally changing erlang's nth behavior? Is this actually a good idea?

I think there is a problem with it. It implies that the Nth value of the list is nil (if it is out-of-boundaries) which is NOT true. It essentially obscures the problem and goes against Erlang's "fail fast" approach

@josevalim
josevalim Jul 11, 2012

so intentionally changing erlang's nth behavior? Is this actually a good idea?

Enum has different semantics than most of functions in lists. Check all? and any? for example which are non strict.

It implies the Nth value of the list is nil (if it is out-of-boundaries) which is NOT true

It does not imply that. It implies that when out of range, it returns nil instead of failing. The main point here is to not raise an exception so the calling code can better handle the scenario. I am also fine with adding a strict version of nth too (probably nth!).

@yrashk
yrashk Jul 11, 2012

It absolutely does imply that

iex(2)> l = [1,2,3,nil]
[1,2,3,nil]
iex(3)> Enum.nth l, 4
nil
iex(4)> Enum.nth l, 5
nil

There's no way to know whether that was an OOB or an actual value

@josevalim
josevalim Jul 11, 2012

If it matters or not is up to the calling code. For a code that expects something to be returned, it doesn't matter if the item is nil or the list is not filled, its requirements were not fulfilled.

+ end
+
+ def nth(collection, n) when n > 0 do
+ case O.iterator(collection) do
+ { iterator, pointer } ->
+ do_nth(pointer, iterator, n)
+ list when is_list(list) ->
+ do_nth(list, n)
+ end
+ end
+
+ @doc """
Partitions `collection` into two where the first one contains elements
for which `fun` returns a truthy value, and the second one -- for which `fun`
returns false or nil.
@@ -926,6 +952,16 @@ defmodule Enum do
[]
end
+ ## nth
+
+ defp do_nth([h|_], 1), do: h
+ defp do_nth([_|t], n), do: do_nth(t, n - 1)
+ defp do_nth([], _), do: nil
+
+ defp do_nth({ h, _next }, _iterator, 1), do: h
+ defp do_nth({ _, next }, iterator, n), do: do_nth(iterator.(next), iterator, n - 1)
+ defp do_nth(:stop, _iterator, _), do: nil
+
## reduce
defp do_reduce({ h, next }, iterator, acc, fun) do
View
13 lib/elixir/lib/iex.ex
@@ -122,11 +122,9 @@ defmodule IEx do
io.put result
- config = config.binding(new_binding).cache('').
- scope(scope).result(result).increment_counter
-
- update_history(config)
- config
+ config = config.result(result)
+ update_history(config.cache(code).scope(nil))
+ config.increment_counter.cache('').binding(new_binding).scope(scope)
rescue
TokenMissingError ->
config.cache(code)
@@ -148,10 +146,7 @@ defmodule IEx do
defp update_history(config) do
current = Process.get :iex_history
-
- # Disregard the scope since we won't need
- # it and it is considerably large.
- Process.put :iex_history, [config.scope(nil)|current]
+ Process.put :iex_history, [config|current]
end
defp print_stacktrace(io, stacktrace) do
View
19 lib/elixir/lib/iex/helpers.ex
@@ -40,12 +40,13 @@ defmodule IEx.Helpers do
Prints the history
"""
def h do
- history = List.reverse(Process.get(:__history__))
- lc {item, index} inlist List.zip(history,
- :lists.seq(1,length(history))) do
- IO.puts "## #{index}:\n#{item[:code]}#=> #{inspect item[:result]}"
- end
- nil
+ history = List.reverse(Process.get(:iex_history))
+ Enum.each(history, print_history(&1))
+ :ok
+ end
+
+ defp print_history(config) do
+ IO.puts "#{config.counter}: #{config.cache}#=> #{inspect config.result}\n"
end
@doc """
@@ -55,13 +56,11 @@ defmodule IEx.Helpers do
"""
def v(n) when n < 0 do
history = Process.get(:iex_history)
- config = :lists.nth(abs(n), history)
- config.result
+ if config = Enum.nth(history, abs(n)), do: config.result
end
def v(n) do
history = Process.get(:iex_history) /> List.reverse
- config = :lists.nth(n, history)
- config.result
+ if config = Enum.nth(history, n), do: config.result
end
end
View
21 lib/elixir/lib/list.ex
@@ -276,7 +276,6 @@ defmodule List do
:lists.sort list
end
-
@doc """
Sorts the list according to an ordering function. fun(a, b) should
return true if `a` compares less than or equal to `b`, `false` otherwise.
@@ -320,26 +319,6 @@ defmodule List do
end
@doc """
- Looks for a term in a list and returns its position.
- If term is found in the first position, return 1.
- If no terms not found in list, the return value is nil.
-
- ### Examples
-
- List.find_index ['a'], 'b'
- #=> nil
- List.find_index ['a'], 'a'
- #=> 1
- """
- def find_index(list, term) do
- index = Erlang.string.str(list, [term])
- case index == 0 do
- true -> nil
- false -> index
- end
- end
-
- @doc """
Wraps the argument in a list.
If the argument is already a list, returns the list.
If the argument is nil, returns an empty list.
View
12 lib/elixir/test/elixir/enum_test.exs
@@ -166,6 +166,12 @@ defmodule EnumTest.List do
assert Enum.map_reduce([1,2,3], 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
end
+ test :nth do
+ assert Enum.nth([2,4,6], 1) == 2
+ assert Enum.nth([2,4,6], 3) == 6
+ assert Enum.nth([2,4,6], 5) == nil
+ end
+
test :partition do
assert Enum.partition([1,2,3], fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
assert Enum.partition([2,4,6], fn(x) -> rem(x, 2) == 0 end) == { [2,4,6], [] }
@@ -627,6 +633,12 @@ defmodule EnumTest.Range do
assert Enum.map_reduce(range, 1, fn(x, acc) -> { x * 2, x + acc } end) == { [2,4,6], 7 }
end
+ test :nth do
+ assert Enum.nth(2..6, 1) == 2
+ assert Enum.nth(2..6, 5) == 6
+ assert Enum.nth(2..6, 9) == nil
+ end
+
test :partition do
range = Range.new(first: 1, last: 3)
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1,3] }
View
7 lib/elixir/test/elixir/list_test.exs
@@ -91,13 +91,6 @@ defmodule ListTest do
assert List.duplicate([1], 1) == [[1]]
end
- test :find_index do
- assert List.find_index([], 'a') == nil
- assert List.find_index(['a'], 'b') == nil
- assert List.find_index(['a'], 'a') == 1
- assert List.find_index([1,2,4,3], 3) == 4
- end
-
test :last do
assert List.last([]) == nil
assert List.last([1]) == 1

0 comments on commit ae4bbed

Please sign in to comment.