Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide an Iterable/Iterator protocol #148

Open
masak opened this issue Jun 13, 2016 · 3 comments
Open

Provide an Iterable/Iterator protocol #148

masak opened this issue Jun 13, 2016 · 3 comments

Comments

@masak
Copy link
Owner

masak commented Jun 13, 2016

Let's say someone wanted to implement a Range type in 007. (In a world where #32 is a fact, let's say.)

class Range {
    my minimum;
    my maximum;
}

Cool, but when they try to loop over their new range, it won't work, because Q::Statement::For loves Arrays and nothing else:

my $array = $.expr.eval($runtime);
die X::TypeCheck.new(:operation("for loop"), :got($array), :expected(Val::Array))
    unless $array ~~ Val::Array;

I propose changing the check from Val::Array to a duck-typing check for a method called .iterator. Under a #33 scheme, the check would be against this kind of interface:

interface Iterable {
    has iterator: Iterator;
}

As to the interface of an Iterator, there are a few established patterns out there:

  • JavaScript iterators have a next method returning an object with a done property and (if done is false) a value property.
  • Python has a next method which either returns the next value or throws a StopIteration exception when it's out.
  • Java has a next method (which can throw a NoSuchElementException if you iterate too much), and a separate hasNext method to check beforehand.

Note that these three are three separate solutions to the semipredicate/out-of-band problem — you can't use any particular value to signify "no more elements", because any value is a possible element.

But all of them are fumbling, incomplete implementations of an enum (see #34), so let's try to do it with sum types.

enum IteratorResult {
    | NextValue(_)
    | IteratorEnd
}

This puts people who use case matching in a very nice position where there's no way to accidentally forget to handle the IteratorEnd case.

I'm not sure we will make enums and case statements core yet. If we don't, then there needs to be a way to access NextValue without a case statement. (But that way needs to be forbidden once the user imports enums and case statements.) Hm, I guess that's a weak case for enums in core.

@masak
Copy link
Owner Author

masak commented Jun 26, 2016

Also worth pointing out that we'd want to make the following Q types implement Iterable:

  • Q::ArgumentList
  • Q::ParameterList
  • Q::PropertyList
  • Q::TraitList
  • Q::StatementList

@masak
Copy link
Owner Author

masak commented Aug 27, 2017

Hm, .iterator() should probably be a method, because we can take as many as we want from a given object, and they're orthogonal/independent.

@masak
Copy link
Owner Author

masak commented Oct 9, 2019

Note that these three are three separate solutions to the semipredicate/out-of-band problem — you can't use any particular value to signify "no more elements", because any value is a possible element.

Not only that, but any solution which separates into Java's hasNext and next is vulnerable to the check-then-act problem.

The Java people have realized this in the design of spliterators; their answer seems to be tryAdvance, which (atomically) runs its callback parameter and returns true if there was a remaining element to iterate over, or returns false otherwise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant