Open mndrix opened 9 years ago
I agree that having :Goal
carry arity 2 makes things visually a lot easier. Perhaps making arity 2 the default for iterate/3
, and having another version that works with a Goal/3
, if the need arises to distinguish state from return value would be a good option.
I'm having second thoughts about this. We now have three examples of a 3-arity Goal
being very useful:
In all three cases, the state that's being iterated is different than the values being produced. I'd hate to lose this powerful ability unless we have to.
Maybe we could use reflection to get the 2-arity behavior when it's wanted. Something like:
iterate(Module:Goal,State,List) :-
functor(Goal,Name,Arity),
( N is Arity+3, current_predicate(Module:Name/N) ->
iterate_(Goal,State,List)
; N is Arity+2, current_predicate(Module:Name/N) ->
iterate_(iterate_wrapper(Goal),State,List)
; otherwise ->
throw(iterate_goal_bad_arity)
).
iterate_wrapper(Goal,X0,X,X) :-
call(Goal,X0,X).
Incidentally, as I was working a little bit more with your iterate/3
, I also started noticing just how powerful it was as well. Although your proposition seems like a good compromise, nonetheless.
During discussion with @eazar001 on #15, I realized that our current API for
iterate/3
is perhaps more complicated than it should be. TheGoal
argument has arity 3. That allows one to make a distinction between state values and returned values. However, in most use cases the state value and the returned value are identical.If
Goal
had arity 2, we'd get simpler code. For example:This seems more natural than our current API which requires an auxiliary predicate in each case.
For what it's worth, using
Goal
with arity 2 more closely emulates the successful iterate :: (a -> a) -> a -> [a] function from Haskell's Prelude.