diff --git a/LICENSE.txt b/LICENSE.txt index 0de86ad..c714d55 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 William Quelho Ferreira +Copyright (c) 2021 William Quelho Ferreira Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/config.ld b/config.ld index a4927ee..c16d496 100644 --- a/config.ld +++ b/config.ld @@ -2,4 +2,4 @@ project = "functional" file = "functional.lua" dir = "docs" title = "functional documentation" -description = "Functional programming utilities written in pure lua" +description = "Functional programming utilities written in pure Lua" diff --git a/docs/index.html b/docs/index.html index 1d09684..00eb587 100644 --- a/docs/index.html +++ b/docs/index.html @@ -30,7 +30,6 @@

functional

Contents

@@ -80,7 +79,7 @@

Iterable

Info:

@@ -93,7 +92,7 @@

Functions

Create an Iterator for the iterable. - exports.counter (...) + exports.counter () Iterate over the naturals starting at 1. @@ -179,20 +178,21 @@

Functions

functions into global scope. -

Fields

+

Class Iterator

- + -
_VERSIONIterator._VERSION Module version.
-

Class Iterator

- + + + + @@ -205,8 +205,12 @@

Class Iterator

- - + + + + + + @@ -320,18 +324,12 @@

Returns:

- exports.counter (...) + exports.counter ()
Iterate over the naturals starting at 1. -

Parameters:

-

Returns:

    @@ -1060,12 +1058,12 @@

    Returns:

-

Fields

+

Class Iterator

- - _VERSION + + Iterator._VERSION
Module version. @@ -1077,10 +1075,6 @@

Fields

-
-

Class Iterator

- -
Iterator.create (iterable) @@ -1110,6 +1104,25 @@

Returns:

+ +
+ + Iterator:next () +
+
+ Retrieve the next element from the iterator. + + + +

Returns:

+
    + + the next value in the sequence +
+ + + +
@@ -1206,11 +1219,13 @@

Returns:

- - Iterator.from_iterated_call (func) + + Iterator.from (func, is, var)
- Iterate over the function's returned values upon repeated calls + Iterate over the function's returned values upon repeated calls. + This can effectively convert a vanilla-Lua iterator into a functional-style + one (e.g., Iterator.from(io.lines "my_file.txt") gives you a string iterator).

Parameters:

@@ -1219,6 +1234,12 @@

Parameters:

function the function to call +
  • is + invariant state passed to func +
  • +
  • var + initial variable passed to func +
  • Returns:

    @@ -1231,6 +1252,42 @@

    Returns:

    +
    +
    + + Iterator.packed_from (func, is, var) +
    +
    + Iterate over the function's returned values (packed into a table) upon repeated calls. + This is similar to Iterator.from, but instead of the created Iterator + generating multiple return values per call, it returns them all + packed into an array. + + +

    Parameters:

    + + +

    Returns:

    +
      + + iterator + the new Iterator +
    + + + +
    @@ -1737,7 +1794,7 @@

    Returns:

    generated by LDoc 1.4.6 -Last updated 2021-09-05 23:41:35 +Last updated 2021-09-09 18:35:44
    diff --git a/functional-1.2-0.rockspec b/functional-1.3-0.rockspec similarity index 92% rename from functional-1.2-0.rockspec rename to functional-1.3-0.rockspec index bd32d1a..e783148 100644 --- a/functional-1.2-0.rockspec +++ b/functional-1.3-0.rockspec @@ -1,8 +1,8 @@ package = "functional" -version = "1.2-0" +version = "1.3-0" source = { url = "git://github.com/wqferr/functional", - tag = "v1.2.0" + tag = "v1.3.0" } description = { diff --git a/functional.d.tl b/functional.d.tl index 6972d62..bfa819b 100644 --- a/functional.d.tl +++ b/functional.d.tl @@ -6,13 +6,24 @@ local record module type mapping = function(T): U type reducer = function(A, T): A + type vanilla_singlearg_iter = function(IS, T): T + + -- FIXME in order to properly support iterators with multiple + -- types per return, + -- https://github.com/teal-language/tl/pull/449 + -- needs to be merged + type vanilla_multiarg_iter = function(IS, ...: T): T... + record Iterator create: function({T}): Iterator create: function(Iterator): Iterator counter: function(): Iterator - range: function(integer, integer, integer): Iterator + + from: function(vanilla_singlearg_iter, IS, T): Iterator + packed_from: function(vanilla_multiarg_iter, IS, T): Iterator<{T}> + from_coroutine: function(thread): Iterator - from_iterated_call: function(producer): Iterator + range: function(integer, integer, integer): Iterator clone: function(Iterator): Iterator is_complete: function(Iterator): boolean @@ -23,7 +34,6 @@ local record module filter: function(Iterator, predicate): Iterator map: function(Iterator, mapping): Iterator - reduce: function(Iterator, reducer): T reduce: function(Iterator, reducer, A): A foreach: function(Iterator, consumer) @@ -72,10 +82,14 @@ local record module every: function(Iterator, integer): Iterator any: function({T}, predicate): boolean + any: function(Iterator, predicate): boolean all: function({T}, predicate): boolean + all: function(Iterator, predicate): boolean - to_array: function({T}): {T} + -- Suggestion to change order of these declarations by + -- GitHub user FractalU to_array: function(Iterator): {T} + to_array: function({T}): {T} to_coroutine: function({T}): thread to_coroutine: function(Iterator): thread diff --git a/functional.lua b/functional.lua index ab584f7..b37594c 100644 --- a/functional.lua +++ b/functional.lua @@ -32,7 +32,7 @@ --

    -- @module functional -- @alias M --- @release 1.2.0 +-- @release 1.3.0 -- @author William Quelho Ferreira -- @copyright 2021 -- @license MIT @@ -41,13 +41,14 @@ local M = {} local exports = {} local internal = {} +--- @type Iterator local Iterator = {} local iter_meta = {} local unpack = table.unpack or unpack --- Module version. -M._VERSION = "1.2.0" +M._VERSION = "1.3.0" --- @type Iterator @@ -72,6 +73,10 @@ function Iterator.create(iterable) end end +--- Retrieve the next element from the iterator. +-- @return the next value in the sequence +function Iterator:next() end + --- Iterate over the naturals starting at 1. -- @treturn Iterator the counter -- @see Iterator:take @@ -132,18 +137,38 @@ function Iterator.from_coroutine(co) return internal.wrap_coroutine(co) end ---- Iterate over the function's returned values upon repeated calls +--- Iterate over the function's returned values upon repeated calls. +-- This can effectively convert a vanilla-Lua iterator into a functional-style +-- one (e.g., Iterator.from(io.lines "my_file.txt") gives you a string iterator). -- @tparam function func the function to call +-- @param is invariant state passed to func +-- @param var initial variable passed to func -- @treturn Iterator the new @{Iterator} -function Iterator.from_iterated_call(func) +function Iterator.from(func, is, var) internal.assert_not_nil(func, "func") local iterator = internal.base_iter(nil, internal.func_call_next, internal.func_try_clone) iterator.func = func + iterator.is = is + iterator.var = var return iterator end +--- Iterate over the function's returned values (packed into a table) upon repeated calls. +-- This is similar to @{Iterator.from}, but instead of the created Iterator +-- generating multiple return values per call, it returns them all +-- packed into an array. +-- @tparam function func the function to call +-- @param is invariant state passed to func +-- @param var initial variable passed to fund +-- @treturn iterator the new @{Iterator} +function Iterator.packed_from(func, is, var) + internal.assert_not_nil(func, "func") + local iterator = Iterator.from(func, is, var) + return iterator:map(internal.pack) +end + --- Nondestructively return an indepent iterable from the given one. --

    If iterablet is an Iterator, clone it according -- to its subtype. If iterable is an array, then @@ -409,8 +434,8 @@ end -- @see Iterator:take -- @see Iterator:skip -- @see Iterator:every -function exports.counter(...) - return Iterator.counter(...) +function exports.counter() + return Iterator.counter() end --- Create an integer iterator that goes from start to stop, step-wise. @@ -564,7 +589,7 @@ end -- @see iterate -- @function to_array function M.to_array(iterable) - assert_table(iterable, "iterable") + internal.assert_table(iterable, "iterable") if internal.is_iterator(iterable) then return iterable:to_array() else @@ -678,7 +703,7 @@ end -- @param value the constant to be returned -- @treturn function the constant function function M.constant(value) - return function(...) + return function() return value end end @@ -724,6 +749,10 @@ function internal.func_nil_guard(value, ...) return value, ... end +function internal.pack(...) + return {...} +end + -- ITER FUNCTIONS -- function internal.base_iter(values, next_f, clone) @@ -898,7 +927,7 @@ function internal.skip_next(iter) end while iter.n_remaining > 0 do - local v = iter.values:next() + iter.values:next() iter.n_remaining = iter.n_remaining - 1 end @@ -924,7 +953,7 @@ function internal.every_next(iter) if iter.first_call then iter.first_call = nil else - for i = 1, iter.n - 1 do + for _ = 1, iter.n - 1 do iter.values:next() end end @@ -952,7 +981,7 @@ function internal.iter_coroutine_next(iter) if iter.completed then return nil end - local yield = {coroutine.resume(co)} + local yield = {coroutine.resume(iter.coroutine)} local status = yield[1] assert(status, yield[2]) @@ -965,7 +994,7 @@ function internal.iter_coroutine_next(iter) return unpack(next_value) end -function internal.coroutine_try_clone(iter) +function internal.coroutine_try_clone() error(internal.ERR_COROUTINE_CLONE) end @@ -979,16 +1008,17 @@ function internal.func_call_next(iter) if iter.completed then return nil end - local result = {iter.func()} + local result = {iter.func(iter.is, iter.var)} if #result == 0 then iter.completed = true return nil end + iter.var = result[1] return unpack(result) end -function internal.func_try_clone(iter) +function internal.func_try_clone() error(internal.ERR_FUNCTION_CLONE) end diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..d7c54ad --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +seq.txt diff --git a/test/test.tl b/test/test.tl new file mode 100644 index 0000000..3cc09d2 --- /dev/null +++ b/test/test.tl @@ -0,0 +1,71 @@ +local f = require 'functional' +local test_n = 0 + +local function test_successful() + test_n = test_n + 1 + print("Passed test " .. test_n) +end + +local function expect(iter: f.Iterator, correct: {integer}) + local result = iter:to_array() + assert(#result == #correct) + for i = 1, #result do + assert(result[i] == correct[i]) + end + test_successful() +end + +local function is_odd(x: integer): boolean + return x % 2 == 1 +end + +local seq = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} +expect(f.iterate(seq), seq) +expect(f.iterate(seq):filter(is_odd), {1, 3, 5, 7, 9}) +expect(f.iterate(seq):filter(f.negate(is_odd)), {2, 4, 6, 8, 10}) + +do + local function test_co_f() + local a, b = 0, 1 + while true do + coroutine.yield(a) + a, b = b, a+b + end + end + local test_co = coroutine.create(test_co_f) + local iter_co = f.Iterator.from_coroutine(test_co):take(10) as f.Iterator + expect(iter_co, {0, 1, 1, 2, 3, 5, 8, 13, 21, 34}) +end + +do + local file = io.open("seq.txt", "w+") + for i in f.range(10) do + file:write(i.."\n") + end + file:close() +end + +do + -- reads from file from last test + local read_iter = f.Iterator.from_iter(io.lines "seq.txt"):map(tonumber as f.mapping) + expect(read_iter, f.range(10):to_array()) +end + +do + local test_co = f.range(3, 10, 2):to_coroutine() + local function cont(): integer + local _, n: boolean, any = coroutine.resume(test_co) + return n as integer + end + assert(cont() == 3) + assert(coroutine.status(test_co) == "suspended") + assert(cont() == 5) + assert(coroutine.status(test_co) == "suspended") + assert(cont() == 7) + assert(coroutine.status(test_co) == "suspended") + assert(cont() == 9) + assert(coroutine.status(test_co) == "suspended") + assert(cont() == nil) + assert(coroutine.status(test_co) == "dead") + test_successful() +end diff --git a/test/tlconfig.lua b/test/tlconfig.lua new file mode 100644 index 0000000..6a1bb56 --- /dev/null +++ b/test/tlconfig.lua @@ -0,0 +1,3 @@ +return { + include_dir = {".."} +}

    Iterator.create (iterable) Iterate over the given iterable.
    Iterator:next ()Retrieve the next element from the iterator.
    Iterator.counter () Iterate over the naturals starting at 1.
    Iterate over the coroutine's yielded values.
    Iterator.from_iterated_call (func)Iterate over the function's returned values upon repeated callsIterator.from (func, is, var)Iterate over the function's returned values upon repeated calls.
    Iterator.packed_from (func, is, var)Iterate over the function's returned values (packed into a table) upon repeated calls.
    Iterator.clone (iterable)