[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Microlight
- From: Jay Carlson <nop@...>
- Date: Fri, 21 Dec 2012 10:28:53 -0500
On Dec 21, 2012, at 6:13 AM, Philipp Janda wrote:
> Am 20.12.2012 23:12, schrieb Jay Carlson:
>>
>> I thought it was a shame you can't write a no-memory iterator that applies swap() to another iterator's stepping. (You can't because the "next step" function is only called with *first* value it previously returned.)
>
> That is not the (only) reason. Since you are wrapping another iterator, you need to store its iterator function somewhere.
If the programmer "knows" the iterator is stateless this can be curried with something like
function swaps_for_stateless_iterator( xpairs )
local dummy = {}
local xnext = xpairs( dummy, nil )
local function xnext_swapped(,...) return swap(xnext(swap(...))) end
return xnext_swapped
end
local swaps_ipairs = swaps_for_stateless_iterator( ipairs )
for v,k in swaps_ipairs(t) do ... end
This creates one closure per wrapped iterator rather than one per loop. To really make this extreme, memoize:
swaps_for_stateless_iterator_memoized = memoize(swaps_for_stateless_iterator)
function psychotic_swaps(xpairs, t)
return swaps_for_stateless_iterator_memoized( xpairs ), t, nil
end
for v,k in psychotic_swaps(ipairs, t) do ... end
This looks increasingly deranged since it depends on out-of-band knowledge that an iterator is stateless. Once you go higher-order and change the signature to swaps(xpairs, t) a much simpler implementation is available.
> -- icollect would always take the 2nd value
> icollect( {}, pairs( t ) ) --> values( t )
> icollect( {}, dups1( pairs( t ) ) ) --> keys( t )
Using that dummy argument:
for _,v,k in dups1(ipairs, t) do ... end
> - some iterators (e.g. string.gmatch) can generate more than two values, so we might need
> collect( {}, swap_1_4_and_2_3, s:gmatch( "..." ) )
> - using some arbitrary indices (even when it's 1 and 2) implicitly is worse than passing numbers
> - I find numbers pretty clear, and there is precedent (select, table.insert, etc.), and you could make the numbers optional for a common case (alternatively you could make the table optional)
Although the full collect/icollect versions are clearly the general case, I think something like microlight needs to ship the most common specialized versions, because otherwise I'm just going to end up writing
function values(t) return icollect( {}, 2, pairs(t) ) end
and the *point* of ml was to stop writing boilerplate code over and over again.
On the other hand, writing the "function values(t)" as I did above provides a very clear specification of behavior. So perhaps we can't agree on names for things like "what should we call keys()/values()" but we can have common semantics--or at least terminology.
Jay