@@ -83,7 +83,7 @@ defmodule Stream do
8383 like `Stream.cycle/1`, `Stream.unfold/2`, `Stream.resource/3` and more.
8484 """
8585
86- defrecord Lazy , enum: nil , funs: [ ] , accs: [ ] , done : nil
86+ defrecord Lazy , enum: nil , funs: [ ] , accs: [ ] , after: [ ] , last : nil
8787
8888 defimpl Enumerable , for: Lazy do
8989 @ compile :inline_list_funs
@@ -103,35 +103,32 @@ defmodule Stream do
103103 { :error , __MODULE__ }
104104 end
105105
106- defp do_reduce ( Lazy [ enum : enum , funs: funs , accs: accs , done: done ] , acc , fun ) do
106+ defp do_reduce ( Lazy [ enum : enum , funs: funs , accs: accs , last: last , after: after_funs ] , acc , fun ) do
107107 composed = :lists . foldl ( fn fun , acc -> fun . ( acc ) end , fun , funs )
108- do_each ( & Enumerable . reduce ( enum , & 1 , composed ) , done && { done , fun } , :lists . reverse ( accs ) , acc )
108+ do_each ( & Enumerable . reduce ( enum , & 1 , composed ) , after_funs ,
109+ last && { last , fun } , :lists . reverse ( accs ) , acc )
109110 end
110111
111- defp do_each ( _reduce , _done , _accs , { :halt , acc } ) do
112- { :halted , acc }
113- end
114-
115- defp do_each ( reduce , done , accs , { :suspend , acc } ) do
116- { :suspended , acc , & do_each ( reduce , done , accs , & 1 ) }
117- end
118-
119- defp do_each ( reduce , done , accs , { :cont , acc } ) do
120- case reduce . ( { :cont , [ acc | accs ] } ) do
112+ defp do_each ( reduce , after_funs , last , accs , { command , acc } ) do
113+ case reduce . ( { command , [ acc | accs ] } ) do
121114 { :suspended , [ acc | accs ] , continuation } ->
122- { :suspended , acc , & do_each ( continuation , done , accs , & 1 ) }
115+ { :suspended , acc , & do_each ( continuation , after_funs , last , accs , & 1 ) }
123116 { :halted , [ acc | _ ] } ->
117+ lc fun inlist after_funs , do: fun . ( )
124118 { :halted , acc }
125119 { :done , [ acc | _ ] = accs } ->
126- case done do
120+ case last do
127121 nil ->
122+ lc fun inlist after_funs , do: fun . ( )
128123 { :done , acc }
129- { done , fun } ->
130- case done . ( fun ) . ( accs ) do
124+ { last , fun } ->
125+ res = case last . ( fun ) . ( accs ) do
131126 { :cont , [ acc | _ ] } -> { :done , acc }
132127 { :halt , [ acc | _ ] } -> { :halted , acc }
133128 { :suspend , [ acc | _ ] } -> { :suspended , acc , & ( { :done , & 1 |> elem ( 1 ) } ) }
134129 end
130+ lc fun inlist after_funs , do: fun . ( )
131+ res
135132 end
136133 end
137134 end
@@ -163,10 +160,30 @@ defmodule Stream do
163160 ## Transformers
164161
165162 @ doc """
166- Shortcut to `chunks(coll, n, n)`.
163+ Executes the given function when the stream is done, halted
164+ or an error happened during streaming. Useful for resource
165+ clean up.
166+
167+ Callbacks registered later will be executed earlier.
168+
169+ ## Examples
170+
171+ iex> stream = Stream.after [1,2,3], fn -> Process.put(:done, true) end
172+ iex> Enum.to_list(stream)
173+ [1,2,3]
174+ iex> Process.get(:done)
175+ true
176+
177+ """
178+ @ spec unquote ( :after ) ( Enumerable . t , ( ( ) -> term ) ) :: Enumerable . t
179+ def unquote ( :after ) ( Lazy [ after : funs ] = lazy , fun ) , do: lazy . after ( [ fun | funs ] )
180+ def unquote ( :after ) ( enum , fun ) , do: Lazy [ enum : enum , after: [ fun ] ]
181+
182+ @ doc """
183+ Shortcut to `chunks(enum, n, n)`.
167184 """
168185 @ spec chunks ( Enumerable . t , non_neg_integer ) :: Enumerable . t
169- def chunks ( coll , n ) , do: chunks ( coll , n , n , nil )
186+ def chunks ( enum , n ) , do: chunks ( enum , n , n , nil )
170187
171188 @ doc """
172189 Streams the enumerable in chunks, containing `n` items each, where
@@ -487,6 +504,30 @@ defmodule Stream do
487504 lazy enum , fn ( f1 ) -> R . reject ( fun , f1 ) end
488505 end
489506
507+ @ doc """
508+ Runs the given stream.
509+
510+ This is useful when a stream needs to be run, for side effects,
511+ and there is no interest in its return result.
512+
513+ ## Examples
514+
515+ Open up a file, replace all # by % and stream to another file
516+ without loading the whole file in memory:
517+
518+ stream = File.stream!("code")
519+ |> Stream.map(&String.replace(&1, "#", "%"))
520+ |> File.stream_to!("new")
521+
522+ No computation will be done until we call one of the Enum functions
523+ or `Stream.run/1`.
524+ """
525+ @ spec run ( Enumerable . t ) :: :ok
526+ def run ( stream ) do
527+ Enumerable . reduce ( stream , { :cont , nil } , fn ( _ , _ ) -> { :cont , nil } end )
528+ :ok
529+ end
530+
490531 @ doc """
491532 Lazily takes the next `n` items from the enumerable and stops
492533 enumeration.
@@ -911,8 +952,8 @@ defmodule Stream do
911952 defp lazy ( enum , acc , fun ) ,
912953 do: Lazy [ enum : enum , funs: [ fun ] , accs: [ acc ] ]
913954
914- defp lazy ( Lazy [ done : nil , funs: funs , accs: accs ] = lazy , acc , fun , done ) ,
915- do: lazy . funs ( [ fun | funs ] ) . accs ( [ acc | accs ] ) . done ( done )
916- defp lazy ( enum , acc , fun , done ) ,
917- do: Lazy [ enum : enum , funs: [ fun ] , accs: [ acc ] , done: done ]
955+ defp lazy ( Lazy [ last : nil , funs: funs , accs: accs ] = lazy , acc , fun , last ) ,
956+ do: lazy . funs ( [ fun | funs ] ) . accs ( [ acc | accs ] ) . last ( last )
957+ defp lazy ( enum , acc , fun , last ) ,
958+ do: Lazy [ enum : enum , funs: [ fun ] , accs: [ acc ] , last: last ]
918959end
0 commit comments