Skip to content

Instantly share code, notes, and snippets.

@ToJans
Last active December 31, 2015 06:29
Show Gist options
  • Save ToJans/7948100 to your computer and use it in GitHub Desktop.
Save ToJans/7948100 to your computer and use it in GitHub Desktop.
An attempt for proper Enumerators in Elixir
defmodule Seqable do
defprotocol ChuckNorris do
def value?(continuation)
def value(continuation)
def next(continuation)
end
defimpl ChuckNorris, for: List do
def value?([]), do: false
def value?(_), do: true
def value([]), do: :undefined
def value(l), do: hd(l)
def next(l), do: tl(l)
end
defrecord Continuation, done: false, source: [], value: :undefined
def get_next(stream, modifier//fn x-> x end) do
done = ChuckNorris.value?(stream)==false
value = ChuckNorris.value(stream)
next_source = ChuckNorris.next(stream)
new_source = fn() -> modifier.(next_source) end
Continuation[done: done, source: new_source, value: value]
end
defimpl ChuckNorris, for: Continuation do
def value?(continuation), do: continuation.done == false
def value(continuation), do: continuation.value
def next(continuation=Continuation[done: true]), do: continuation
def next(continuation=Continuation[source: source]) do
case is_function(source) do
false -> Seqable.get_next(continuation.source)
true -> continuation.source.()
end
end
end
defimpl Enumerable, for: Continuation do
def count(cont), do: count(cont, 0)
defp count(Continuation[done: true], n), do: n
defp count(cont, n), do: count(ChuckNorris.next(cont), n+1)
def member?(cont, what), do: throw :not_implemented
def reduce(cont, acc, func) do
IO.inspect([cont, acc, func])
case ChuckNorris.value?(cont) do
false -> acc
true ->
val = ChuckNorris.value(cont)
new_acc = func.(val, acc)
reduce(ChuckNorris.next(cont), new_acc, func)
end
end
end
def start(_type, _args), do: {:ok, self}
def filter(stream,func) do
case ChuckNorris.value?(stream) do
false -> Continuation[done: true, source: stream, value: :undefined]
true ->
case func.(ChuckNorris.value(stream)) do
true -> get_next(stream,&(filter(&1,func)) )
false -> filter(ChuckNorris.next(stream),func)
end
end
end
def take(stream,0) do
Continuation[done: true, source: stream, value: :undefined]
end
def take(stream, n) do
case ChuckNorris.value?(stream) do
false -> Continuation[done: true, source: stream, value: :undefined]
true -> get_next(stream, &(take(&1,n-1)))
end
end
def skip(stream, 0) do
stream
end
def skip(stream, n) do
case ChuckNorris.value?(stream) do
false -> stream
true -> skip(ChuckNorris.next(stream),n-1)
end
end
defrecord CompositeChuckNorris, streams: []
defimpl ChuckNorris, for: CompositeChuckNorris do
def value?(CompositeChuckNorris[streams: []]), do: false
def value?(CompositeChuckNorris[streams: [current|_]]), do: ChuckNorris.value?(current)
def value(CompositeChuckNorris[streams: []]), do: :undefined
def value(CompositeChuckNorris[streams: [current|_]]), do: ChuckNorris.value(current)
def next(c = CompositeChuckNorris[streams: []]), do: c
def next(CompositeChuckNorris[streams: [c]]), do: ChuckNorris.next(c)
def next(CompositeChuckNorris[streams: [current|next]]) do
n = ChuckNorris.next(current)
case ChuckNorris.value?(n) do
false -> ChuckNorris.next(CompositeChuckNorris[streams: next])
true -> CompositeChuckNorris[streams: next ++ [current]]
end
end
end
def interleave(streams) do
CompositeChuckNorris[streams: streams]
end
end
ExUnit.start
defmodule ChuckNorrisTest do
use ExUnit.Case
test "list" do
list = [1, 2, 3, 4, 5]
list2 = [:a, :b]
assert 15 == Enum.reduce(list, 0, &add_numbers/2)
assert [1, 3, 5] == Enum.to_list(Seqable.filter(list, &odd/1))
assert [1, 2, 3] == Enum.to_list(Seqable.take(list, 3))
assert [3, 4, 5] == Enum.to_list(Seqable.skip(list, 2))
#assert [:a,1,:b,2,3,4,5] == Enum.to_list(Seqable.interleave([list2,list]))
end
defp add_numbers(a,b), do: a+b
defp odd(x), do: rem(x,2) == 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment