Skip to content

Commit

Permalink
Add multidim arr concat op funcs
Browse files Browse the repository at this point in the history
Refs   #809.
  • Loading branch information
evhub committed Nov 29, 2023
1 parent 5ab6591 commit e2befe6
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 39 deletions.
8 changes: 7 additions & 1 deletion DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ To allow for better use of [`numpy`](https://numpy.org/) objects in Coconut, all
- Coconut's [multidimensional array literal and array concatenation syntax](#multidimensional-array-literalconcatenation-syntax) supports `numpy` objects, including using fast `numpy` concatenation methods if given `numpy` arrays rather than Coconut's default much slower implementation built for Python lists of lists.
- Many of Coconut's built-ins include special `numpy` support, specifically:
* [`fmap`](#fmap) will use [`numpy.vectorize`](https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html) to map over `numpy` arrays.
* [`multi_enumerate`](#multi_enumerate) allows for easily looping over all the multi-dimensional indices in a `numpy` array.
* [`multi_enumerate`](#multi_enumerate) allows for easily looping over all the multidimensional indices in a `numpy` array.
* [`cartesian_product`](#cartesian_product) can compute the Cartesian product of given `numpy` arrays as a `numpy` array.
* [`all_equal`](#all_equal) allows for easily checking if all the elements in a `numpy` array are the same.
- [`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html) is registered as a [`collections.abc.Sequence`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence), enabling it to be used in [sequence patterns](#semantics-specification).
Expand Down Expand Up @@ -1822,6 +1822,10 @@ A very common thing to do in functional programming is to make use of function v
(not in) => # negative containment
(assert) => def (cond, msg=None) => assert cond, msg # (but a better msg if msg is None)
(raise) => def (exc=None, from_exc=None) => raise exc from from_exc # or just raise if exc is None
# operator functions for multidimensional array concatenation use brackets:
[;] => def (x, y) => [x; y]
[;;] => def (x, y) => [x;; y]
... # and so on for any number of semicolons
# there are two operator functions that don't require parentheses:
.[] => (operator.getitem)
.$[] => # iterator slicing operator
Expand Down Expand Up @@ -2067,6 +2071,8 @@ If multiple different concatenation operators are used, the operators with the l
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
```

_Note: the [operator functions](#operator-functions) for multidimensional array concatenation are spelled `[;]`, `[;;]`, etc. (for any number of parentheses)._

##### Comparison to Julia

Coconut's multidimensional array syntax is based on that of [Julia](https://docs.julialang.org/en/v1/manual/arrays/#man-array-literals). The primary difference between Coconut's syntax and Julia's syntax is that multidimensional arrays are row-first in Coconut (following `numpy`), but column-first in Julia. Thus, `;` is vertical concatenation in Julia but **horizontal concatenation** in Coconut and `;;` is horizontal concatenation in Julia but **vertical concatenation** in Coconut.
Expand Down
31 changes: 15 additions & 16 deletions __coconut__/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1828,45 +1828,44 @@ def _coconut_mk_anon_namedtuple(


# @_t.overload
# def _coconut_multi_dim_arr(
# arrs: _t.Tuple[_coconut.npt.NDArray[_DType], ...],
# def _coconut_arr_concat_op(
# dim: int,
# *arrs: _coconut.npt.NDArray[_DType],
# ) -> _coconut.npt.NDArray[_DType]: ...
# @_t.overload
# def _coconut_multi_dim_arr(
# arrs: _t.Tuple[_DType, ...],
# def _coconut_arr_concat_op(
# dim: int,
# *arrs: _DType,
# ) -> _coconut.npt.NDArray[_DType]: ...

@_t.overload
def _coconut_multi_dim_arr(
arrs: _t.Tuple[_t.Sequence[_T], ...],
def _coconut_arr_concat_op(
dim: _t.Literal[1],
*arrs: _t.Sequence[_T],
) -> _t.Sequence[_T]: ...
@_t.overload
def _coconut_multi_dim_arr(
arrs: _t.Tuple[_T, ...],
def _coconut_arr_concat_op(
dim: _t.Literal[1],
*arrs: _T,
) -> _t.Sequence[_T]: ...

@_t.overload
def _coconut_multi_dim_arr(
arrs: _t.Tuple[_t.Sequence[_t.Sequence[_T]], ...],
def _coconut_arr_concat_op(
dim: _t.Literal[2],
*arrs: _t.Sequence[_t.Sequence[_T]],
) -> _t.Sequence[_t.Sequence[_T]]: ...
@_t.overload
def _coconut_multi_dim_arr(
arrs: _t.Tuple[_t.Sequence[_T], ...],
def _coconut_arr_concat_op(
dim: _t.Literal[2],
*arrs: _t.Sequence[_T],
) -> _t.Sequence[_t.Sequence[_T]]: ...
@_t.overload
def _coconut_multi_dim_arr(
arrs: _t.Tuple[_T, ...],
def _coconut_arr_concat_op(
dim: _t.Literal[2],
*arrs: _T,
) -> _t.Sequence[_t.Sequence[_T]]: ...

@_t.overload
def _coconut_multi_dim_arr(arrs: _Tuple, dim: int) -> _Sequence: ...
def _coconut_arr_concat_op(dim: int, *arrs: _t.Any) -> _Sequence: ...


class _coconut_SupportsAdd(_t.Protocol, _t.Generic[_Tco, _Ucontra, _Vco]):
Expand Down
2 changes: 1 addition & 1 deletion coconut/__coconut__.pyi
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from __coconut__ import *
from __coconut__ import _coconut_tail_call, _coconut_tco, _coconut_call_set_names, _coconut_handle_cls_kwargs, _coconut_handle_cls_stargs, _namedtuple_of, _coconut, _coconut_Expected, _coconut_MatchError, _coconut_SupportsAdd, _coconut_SupportsMinus, _coconut_SupportsMul, _coconut_SupportsPow, _coconut_SupportsTruediv, _coconut_SupportsFloordiv, _coconut_SupportsMod, _coconut_SupportsAnd, _coconut_SupportsXor, _coconut_SupportsOr, _coconut_SupportsLshift, _coconut_SupportsRshift, _coconut_SupportsMatmul, _coconut_SupportsInv, _coconut_Expected, _coconut_MatchError, _coconut_iter_getitem, _coconut_base_compose, _coconut_forward_compose, _coconut_back_compose, _coconut_forward_star_compose, _coconut_back_star_compose, _coconut_forward_dubstar_compose, _coconut_back_dubstar_compose, _coconut_pipe, _coconut_star_pipe, _coconut_dubstar_pipe, _coconut_back_pipe, _coconut_back_star_pipe, _coconut_back_dubstar_pipe, _coconut_none_pipe, _coconut_none_star_pipe, _coconut_none_dubstar_pipe, _coconut_bool_and, _coconut_bool_or, _coconut_none_coalesce, _coconut_minus, _coconut_map, _coconut_partial, _coconut_complex_partial, _coconut_get_function_match_error, _coconut_base_pattern_func, _coconut_addpattern, _coconut_sentinel, _coconut_assert, _coconut_raise, _coconut_mark_as_match, _coconut_reiterable, _coconut_self_match_types, _coconut_dict_merge, _coconut_exec, _coconut_comma_op, _coconut_multi_dim_arr, _coconut_mk_anon_namedtuple, _coconut_matmul, _coconut_py_str, _coconut_flatten, _coconut_multiset, _coconut_back_none_pipe, _coconut_back_none_star_pipe, _coconut_back_none_dubstar_pipe, _coconut_forward_none_compose, _coconut_back_none_compose, _coconut_forward_none_star_compose, _coconut_back_none_star_compose, _coconut_forward_none_dubstar_compose, _coconut_back_none_dubstar_compose, _coconut_call_or_coefficient, _coconut_in, _coconut_not_in, _coconut_attritemgetter
from __coconut__ import _coconut_tail_call, _coconut_tco, _coconut_call_set_names, _coconut_handle_cls_kwargs, _coconut_handle_cls_stargs, _namedtuple_of, _coconut, _coconut_Expected, _coconut_MatchError, _coconut_SupportsAdd, _coconut_SupportsMinus, _coconut_SupportsMul, _coconut_SupportsPow, _coconut_SupportsTruediv, _coconut_SupportsFloordiv, _coconut_SupportsMod, _coconut_SupportsAnd, _coconut_SupportsXor, _coconut_SupportsOr, _coconut_SupportsLshift, _coconut_SupportsRshift, _coconut_SupportsMatmul, _coconut_SupportsInv, _coconut_Expected, _coconut_MatchError, _coconut_iter_getitem, _coconut_base_compose, _coconut_forward_compose, _coconut_back_compose, _coconut_forward_star_compose, _coconut_back_star_compose, _coconut_forward_dubstar_compose, _coconut_back_dubstar_compose, _coconut_pipe, _coconut_star_pipe, _coconut_dubstar_pipe, _coconut_back_pipe, _coconut_back_star_pipe, _coconut_back_dubstar_pipe, _coconut_none_pipe, _coconut_none_star_pipe, _coconut_none_dubstar_pipe, _coconut_bool_and, _coconut_bool_or, _coconut_none_coalesce, _coconut_minus, _coconut_map, _coconut_partial, _coconut_complex_partial, _coconut_get_function_match_error, _coconut_base_pattern_func, _coconut_addpattern, _coconut_sentinel, _coconut_assert, _coconut_raise, _coconut_mark_as_match, _coconut_reiterable, _coconut_self_match_types, _coconut_dict_merge, _coconut_exec, _coconut_comma_op, _coconut_arr_concat_op, _coconut_mk_anon_namedtuple, _coconut_matmul, _coconut_py_str, _coconut_flatten, _coconut_multiset, _coconut_back_none_pipe, _coconut_back_none_star_pipe, _coconut_back_none_dubstar_pipe, _coconut_forward_none_compose, _coconut_back_none_compose, _coconut_forward_none_star_compose, _coconut_back_none_star_compose, _coconut_forward_none_dubstar_compose, _coconut_back_none_dubstar_compose, _coconut_call_or_coefficient, _coconut_in, _coconut_not_in, _coconut_attritemgetter
18 changes: 12 additions & 6 deletions coconut/compiler/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
labeled_group,
any_keyword_in,
any_char,
tuple_str_of,
any_len_perm,
any_len_perm_at_least_one,
boundary,
Expand Down Expand Up @@ -576,20 +575,27 @@ def array_literal_handle(loc, tokens):
array_elems = []
for p in pieces:
if p:
if len(p) > 1:
if p[0].lstrip(";") == "":
raise CoconutDeferredSyntaxError("invalid initial multidimensional array separator or broken-up multidimensional array concatenation operator function", loc)
elif len(p) > 1:
internal_assert(sep_level > 1, "failed to handle array literal tokens", tokens)
subarr_item = array_literal_handle(loc, p)
elif p[0].lstrip(";") == "":
raise CoconutDeferredSyntaxError("naked multidimensional array separators are not allowed", loc)
else:
subarr_item = p[0]
array_elems.append(subarr_item)

# if multidimensional array literal is only separators, compile to implicit partial
if not array_elems:
raise CoconutDeferredSyntaxError("multidimensional array literal cannot be only separators", loc)
if len(pieces) > 2:
raise CoconutDeferredSyntaxError("invalid empty multidimensional array literal or broken-up multidimensional array concatenation operator function", loc)
return "_coconut_partial(_coconut_arr_concat_op, " + str(sep_level) + ")"

# check for initial top-level separators
if not pieces[0]:
raise CoconutDeferredSyntaxError("invalid initial multidimensional array separator", loc)

# build multidimensional array
return "_coconut_multi_dim_arr(" + tuple_str_of(array_elems) + ", " + str(sep_level) + ")"
return "_coconut_arr_concat_op(" + str(sep_level) + ", " + ", ".join(array_elems) + ")"


def typedef_op_item_handle(loc, tokens):
Expand Down
2 changes: 1 addition & 1 deletion coconut/compiler/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ def __anext__(self):
# (extra_format_dict is to keep indentation levels matching)
extra_format_dict = dict(
# when anything is added to this list it must also be added to *both* __coconut__ stub files
underscore_imports="{tco_comma}{call_set_names_comma}{handle_cls_args_comma}_namedtuple_of, _coconut, _coconut_Expected, _coconut_MatchError, _coconut_SupportsAdd, _coconut_SupportsMinus, _coconut_SupportsMul, _coconut_SupportsPow, _coconut_SupportsTruediv, _coconut_SupportsFloordiv, _coconut_SupportsMod, _coconut_SupportsAnd, _coconut_SupportsXor, _coconut_SupportsOr, _coconut_SupportsLshift, _coconut_SupportsRshift, _coconut_SupportsMatmul, _coconut_SupportsInv, _coconut_iter_getitem, _coconut_base_compose, _coconut_forward_compose, _coconut_back_compose, _coconut_forward_star_compose, _coconut_back_star_compose, _coconut_forward_dubstar_compose, _coconut_back_dubstar_compose, _coconut_pipe, _coconut_star_pipe, _coconut_dubstar_pipe, _coconut_back_pipe, _coconut_back_star_pipe, _coconut_back_dubstar_pipe, _coconut_none_pipe, _coconut_none_star_pipe, _coconut_none_dubstar_pipe, _coconut_bool_and, _coconut_bool_or, _coconut_none_coalesce, _coconut_minus, _coconut_map, _coconut_partial, _coconut_complex_partial, _coconut_get_function_match_error, _coconut_base_pattern_func, _coconut_addpattern, _coconut_sentinel, _coconut_assert, _coconut_raise, _coconut_mark_as_match, _coconut_reiterable, _coconut_self_match_types, _coconut_dict_merge, _coconut_exec, _coconut_comma_op, _coconut_multi_dim_arr, _coconut_mk_anon_namedtuple, _coconut_matmul, _coconut_py_str, _coconut_flatten, _coconut_multiset, _coconut_back_none_pipe, _coconut_back_none_star_pipe, _coconut_back_none_dubstar_pipe, _coconut_forward_none_compose, _coconut_back_none_compose, _coconut_forward_none_star_compose, _coconut_back_none_star_compose, _coconut_forward_none_dubstar_compose, _coconut_back_none_dubstar_compose, _coconut_call_or_coefficient, _coconut_in, _coconut_not_in, _coconut_attritemgetter".format(**format_dict),
underscore_imports="{tco_comma}{call_set_names_comma}{handle_cls_args_comma}_namedtuple_of, _coconut, _coconut_Expected, _coconut_MatchError, _coconut_SupportsAdd, _coconut_SupportsMinus, _coconut_SupportsMul, _coconut_SupportsPow, _coconut_SupportsTruediv, _coconut_SupportsFloordiv, _coconut_SupportsMod, _coconut_SupportsAnd, _coconut_SupportsXor, _coconut_SupportsOr, _coconut_SupportsLshift, _coconut_SupportsRshift, _coconut_SupportsMatmul, _coconut_SupportsInv, _coconut_iter_getitem, _coconut_base_compose, _coconut_forward_compose, _coconut_back_compose, _coconut_forward_star_compose, _coconut_back_star_compose, _coconut_forward_dubstar_compose, _coconut_back_dubstar_compose, _coconut_pipe, _coconut_star_pipe, _coconut_dubstar_pipe, _coconut_back_pipe, _coconut_back_star_pipe, _coconut_back_dubstar_pipe, _coconut_none_pipe, _coconut_none_star_pipe, _coconut_none_dubstar_pipe, _coconut_bool_and, _coconut_bool_or, _coconut_none_coalesce, _coconut_minus, _coconut_map, _coconut_partial, _coconut_complex_partial, _coconut_get_function_match_error, _coconut_base_pattern_func, _coconut_addpattern, _coconut_sentinel, _coconut_assert, _coconut_raise, _coconut_mark_as_match, _coconut_reiterable, _coconut_self_match_types, _coconut_dict_merge, _coconut_exec, _coconut_comma_op, _coconut_arr_concat_op, _coconut_mk_anon_namedtuple, _coconut_matmul, _coconut_py_str, _coconut_flatten, _coconut_multiset, _coconut_back_none_pipe, _coconut_back_none_star_pipe, _coconut_back_none_dubstar_pipe, _coconut_forward_none_compose, _coconut_back_none_compose, _coconut_forward_none_star_compose, _coconut_back_none_star_compose, _coconut_forward_none_dubstar_compose, _coconut_back_none_dubstar_compose, _coconut_call_or_coefficient, _coconut_in, _coconut_not_in, _coconut_attritemgetter".format(**format_dict),
import_typing=pycondition(
(3, 5),
if_ge='''
Expand Down
3 changes: 2 additions & 1 deletion coconut/compiler/templates/header.py_template
Original file line number Diff line number Diff line change
Expand Up @@ -2036,7 +2036,8 @@ def _coconut_concatenate(arrs, axis):
if not axis:
return _coconut.list(_coconut.itertools.chain.from_iterable(arrs))
return [_coconut_concatenate(rows, axis - 1) for rows in _coconut.zip(*arrs)]
def _coconut_multi_dim_arr(arrs, dim):
def _coconut_arr_concat_op(dim, *arrs):
"""Coconut multi-dimensional array concatenation operator."""
arr_dims = [_coconut_ndim(a) for a in arrs]
arrs = [_coconut_expand_arr(a, dim - d) if d < dim else a for a, d in _coconut.zip(arrs, arr_dims)]
arr_dims.append(dim)
Expand Down
5 changes: 4 additions & 1 deletion coconut/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,11 @@ def get_path_env_var(env_var, default):
"coconut3",
)

py_syntax_version = 3
mimetype = "text/x-python3"
codemirror_mode = {
"name": "ipython",
"version": 3,
}

all_keywords = keyword_vars + const_vars + reserved_vars

Expand Down
7 changes: 2 additions & 5 deletions coconut/icoconut/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
)
from coconut.constants import (
PY311,
py_syntax_version,
codemirror_mode,
mimetype,
version_banner,
tutorial_url,
Expand Down Expand Up @@ -307,10 +307,7 @@ class CoconutKernel(IPythonKernel, object):
"name": "coconut",
"version": VERSION,
"mimetype": mimetype,
"codemirror_mode": {
"name": "ipython",
"version": py_syntax_version,
},
"codemirror_mode": codemirror_mode,
"pygments_lexer": "coconut",
"file_extension": code_exts[0],
}
Expand Down
2 changes: 1 addition & 1 deletion coconut/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
VERSION = "3.0.4"
VERSION_NAME = None
# False for release, int >= 1 for develop
DEVELOP = 2
DEVELOP = 3
ALPHA = False # for pre releases rather than post releases

assert DEVELOP is False or DEVELOP >= 1, "DEVELOP must be False or an int >= 1"
Expand Down
6 changes: 3 additions & 3 deletions coconut/tests/src/cocotest/agnostic/primary_1.coco
Original file line number Diff line number Diff line change
Expand Up @@ -973,8 +973,8 @@ def primary_test_1() -> bool:
def ret1() = 1
assert ret1() == 1
assert (.,2)(1) == (1, 2) == (1,.)(2)
assert [[];] == []
assert [[];;] == [[]]
assert [[];] == [] == [;]([])
assert [[];;] == [[]] == [;;]([])
assert [1;] == [1] == [[1];]
assert [1;;] == [[1]] == [[1];;]
assert [[[1]];;] == [[1]] == [[1;];;]
Expand Down Expand Up @@ -1009,7 +1009,7 @@ def primary_test_1() -> bool:
5, 6 ;;
7, 8] == [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
a = [1,2 ;; 3,4]
assert [a; a] == [[1,2,1,2], [3,4,3,4]]
assert [a; a] == [[1,2,1,2], [3,4,3,4]] == [;](a, a)
assert [a;; a] == [[1,2],[3,4],[1,2],[3,4]] == [*a, *a]
assert [a ;;; a] == [[[1,2],[3,4]], [[1,2],[3,4]]] == [a, a]
assert [a ;;;; a] == [[a], [a]]
Expand Down
5 changes: 3 additions & 2 deletions coconut/tests/src/cocotest/agnostic/primary_2.coco
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ def primary_test_2() -> bool:
a_dict = {"a": 1, "b": 2}
a_dict |= {"a": 10, "c": 20}
assert a_dict == {"a": 10, "b": 2, "c": 20} == {"a": 1, "b": 2} | {"a": 10, "c": 20}
assert ["abc" ; "def"] == ['abc', 'def']
assert ["abc" ;; "def"] == [['abc'], ['def']]
assert ["abc" ; "def"] == ['abc', 'def'] == [;] <*| ("abc", "def")
assert ["abc" ;; "def"] == [['abc'], ['def']] == [;;] <*| ("abc", "def")
assert {"a":0, "b":1}$[0] == "a"
assert (|0, NotImplemented, 2|)$[1] is NotImplemented
assert m{1, 1, 2} |> fmap$(.+1) == m{2, 2, 3}
Expand Down Expand Up @@ -410,6 +410,7 @@ def primary_test_2() -> bool:
assert 0x == 0 == 0 x
assert 0xff == 255 == 0x100-1
assert 11259375 == 0xabcdef
assert [[] ;; [] ;;;] == [[[], []]]

with process_map.multiple_sequential_calls(): # type: ignore
assert map((+), range(3), range(4)$[:-1], strict=True) |> list == [0, 2, 4] == process_map((+), range(3), range(4)$[:-1], strict=True) |> list # type: ignore
Expand Down
5 changes: 4 additions & 1 deletion coconut/tests/src/extras.coco
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,11 @@ def test_setup_none() -> bool:
assert_raises(-> parse("\\("), CoconutSyntaxError)
assert_raises(-> parse("if a:\n b\n c"), CoconutSyntaxError)
assert_raises(-> parse("_coconut"), CoconutSyntaxError)
assert_raises(-> parse("[;]"), CoconutSyntaxError)
assert_raises(-> parse("[; ;]"), CoconutSyntaxError)
assert_raises(-> parse("[; ;; ;]"), CoconutSyntaxError)
assert_raises(-> parse("[; ; ;;]"), CoconutSyntaxError)
assert_raises(-> parse("[[] ;;; ;; [] ;]"), CoconutSyntaxError)
assert_raises(-> parse("[; []]"), CoconutSyntaxError)
assert_raises(-> parse("f$()"), CoconutSyntaxError)
assert_raises(-> parse("f(**x, y)"), CoconutSyntaxError)
assert_raises(-> parse("def f(x) = return x"), CoconutSyntaxError)
Expand Down

0 comments on commit e2befe6

Please sign in to comment.