In my humble opinion, changing the underlying spec too much is unnecessary.
*Enumerable**.count* already encompasses most of the functionality we are
talking about here.
Currently, if it returns *{:ok, number}* the Enumerable is *finite*. If it
returns *{:error, __MODULE__}* the Enumerable may be *finite*, *Countable*,
or *Uncountable.*
Currently, Streams always return *{:error, __MODULE__}. *Something that
could be done to improve Streams is to keep the length of their inputs.
For example:
[1,2,3]
|> Stream.map(&(&1)) #Stream<[enum: [1, 2, 3], funs: [#Function<123/1 in
Stream.map/2>], count: 3]>
|> Stream.concat([4,5,6]) #Stream<[funs: [#Function<456/2 in
Stream.transform/3>], count 6]>
|> Stream.concat( Stream.unfold(...) ) #Stream<[funs: [#Function<456/2 in
Stream.transform/3>]]> (or with count = nil)
*Enumerable.count *would return the *count* on the Stream struct, or {
*:error, Enumerable.Stream} *
Not only is this *mostly* backwards compatible, but it also makes Streams
more informative to the users.
Doing* infinite/finite *checks behind the scenes will make a lot more sense
the users if that information is exposed and clearly articulated
If this seems like a good solution, I will take a harder look at what
changed would be needed to implement this.
On Tuesday, July 25, 2017 at 9:34:10 AM UTC-7, OvermindDL1 wrote:
>
> Oh I forgot newer container types in C++:
>
> * AssociativeContainer: An AssociativeContainer must also be a Container.
> It allows for lookup based on some arbitrary 'key' (type is defined by the
> container) instead of an index. The elements must be ordered so iterators
> to the same identical container must always result in the same order. This
> has no constraints on lookup speed so it could be constant, or it could be
> linear or worse. This is like an Elixir Keyword list.
>
> * UnorderedAssociativeContainer: An UnorderedAssociativeContainer must
> also be a Container. It allows for lookup based on some arbitrary 'key'
> (type is defined by the container) instead of an index. The elements are
> not ordered so iterators to the same identical container may result in
> different orders each time. This has the constraint that lookup speed must
> be at work linear but is preferred that the average case is either constant
> or logarithmic. This is like an Elixir Map.
>
>
>
> On Tuesday, July 25, 2017 at 10:23:31 AM UTC-6, OvermindDL1 wrote:
>>
>> Just for more data points (not proposing anything), perhaps look at how
>> C++ handles iterables I'll only be referencing the immutable iterator types
>> or the types that can be immutable based on their contained types, I'll
>> also be using the C++ names for them rather than the more traditional
>> OCaml'y/Haskell'y or so names as C++'s is more expressive due to having
>> more 'types':
>>
>> * Iterator: This defines something is an iterator, however it has no way
>> to actually iterate it, it is only a marker trait that says that something
>> is some type of iterator
>>
>> * InputIterator: This is something that you can 'get' something out of,
>> then 'next' to the next element until it 'ends' (or does not end, it could
>> be infinite). You cannot go back over an iteration, it is consume-once
>> thus it is read-once, and you cannot write an element, even of the slot
>> that you are in. The only real Elixir/Erlang 'InputIterator' that I can
>> think of off the top of my mind is the process mailbox.
>>
>> * OutputIterator: This is something that you can 'put' something in to,
>> then next to the next 'slot' to put something in to it as well. You cannot
>> read from an element even of the slot that you are currently in nor can you
>> write to an element multiple times, only once. The only real Elixir/Erlang
>> 'OutputIterator' that I can think of is sending a message to a process.
>>
>> * ForwardIterator: Every ForwardIterator is also an InputIterator and/or
>> an OutputIterator (depending on its contained types), you can get/put and
>> next, however unlike InputIterator/OutputIterator you can read/write
>> from/to a slot multiple times and you can also hold a cursor to a given
>> slot to read/write from/to it again even after you have 'next'/iterated
>> past it. You cannot iterate backwards from a given slot, only forwards.
>> This would be like iterating through a list in Elixir/Erlang.
>>
>> * BidirectionalIterator: Every BidirectionalIterator is also a
>> ForwardIterator, except you can also go backwards from a slot. In
>> Elixir/Erlang this would be like a zipper structure.
>>
>> * RandomAccessIterator: Every RandomAccessIterator is also a
>> BidirectionalIterator except that you can jump to any slot in *constant*
>> time via an index. An Elixir equivalent would be like accessing a tuple.
>>
>>
>>
>> C++ also has traits that define if something is a type of container,
>> these are:
>>
>> * Container: The base Container trait (do note, these are not C++
>> classes, they are 'traits', if something implements the required functions
>> for their type then they are considered to fulfill a given 'trait', there
>> is no inheritence or anything of the sort). This defines something that
>> 'manages' something else. It has a way to acquire a ForwardIterator (or
>> anything else that implements a ForwardIterator, such as a
>> Bidirectionaliterator). This is like an Elixir list or a map or really
>> anything that holds something.
>>
>> * ReversibleContainer: A ReversibleContainer must also be a Container.
>> It adds the constraints that acquiring an iterator must return an iterator
>> that fulfills a Bidirectionaliterator in capability. It also adds the
>> constraint that it must be possible to get an iterator that iterates over
>> the elements in reverse order. This is like an Elixir zipper.
>>
>> * AllocatorAwareContainer: An AllocatorAwareContainer must also be a
>> Container. It is a container that creates it's own elements via a (either
>> passed in or internal) allocator function instead of the user supplying the
>> element. This is like an Elixir Stream.
>>
>> * SequenceContainer: A SequenceContainer must also be a Container. It
>> adds the constraints that the returned iterator must be in a linear
>> arrangement, such as getting different iterators on the same container must
>> always result in the same order and in constant iteration time (thus that
>> 'next' is constant time). This is like iterating over an Elixir List but
>> not a map.
>>
>> * ContiguousContainer: A ContiguousContainer must also be a Container.
>> It adds the constraint that the returned iterator must be of type
>> RandomAccessIterator, thus allowing for constant time element access. This
>> is like an Elixir tuple.
>>
>>
>> A given iterator can fulfill multiple even disjoint iterator traits, like
>> being both an input and an output iterator (in Elixir this would be like a
>> File access interface).
>>
>> A given container can fulfill multiple container traits, like being both
>> a SequenceContainer and a ContiguousContainer, which in Elixir would be a
>> tuple since it fulfills both requirements.
>>
>>
>> Just putting it out there that there are a lot of different kinds of
>> Sequences, and even in C++ you cannot know if an iterator (or even a
>> container) is not 'infinite'. Like take stdin/stdout in C++, those are
>> InputIterator and OutputIterator respectively, yet you do not know if
>> either 'end' until you next() on them and test (and 'next()ing' on the
>> stdin InputIterator may 'wait' in time until more input comes in for
>> example, and writing to an OutputIterator of stdout may 'wait' in time if
>> the buffer is full and the receiving pipe has not processed the buffered
>> data yet, which can also happen when sending/receiving message on the BEAM
>> as well).
>>
>>
>>
>> On Monday, July 24, 2017 at 4:37:41 PM UTC-6, [email protected] wrote:
>>>
>>> This is a good point. I was looking for an example of how these protocol
>>> changes could allow us to make some enumerables not allow negative access.
>>> No existing thing in the language is a good example, though, because we
>>> would break current behaviour.
>>>
>>> It does present the interesting option of adding an `infinite` boolean
>>> field, defaulting to false, to the Stream struct and having the
>>> exhaustiblity protocol function check that. Then stream constructors that
>>> are known to generate infinities like Stream.cycle could opt-in to erroring
>>> on negative access. Stream modifiers known to 'curb' the infinities could
>>> reset it to true. Ambiguous operations would leave it unmodified.
>>>
>>> My exhaustible impl attempt will come well after my take on
>>> Enumerable.fetch, so I'm not really thinking very hard about this yet––just
>>> spitballing. But definitely the intention is not to have exhaustible? do
>>> any work, merely allow Enumerable implementers outside of core to opt-out
>>> of negative integer access if they wish.
>>>
>>
--
You received this message because you are subscribed to the Google Groups
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-core/23acbbd2-99bc-4314-9bc4-ca169d9670fb%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.