├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.clj ├── deps.edn ├── src ├── data_readers.clj └── sweet_array │ └── core.clj ├── template └── pom.xml └── test └── sweet_array └── core_test.clj /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Setup Java 11 | uses: actions/setup-java@v4 12 | with: 13 | distribution: 'temurin' 14 | java-version: '17' 15 | - name: Setup Clojure CLI 16 | uses: DeLaGuardo/setup-clojure@13.1 17 | with: 18 | cli: latest 19 | - name: Cache deps 20 | uses: actions/cache@v4 21 | with: 22 | path: | 23 | .cpcache 24 | ~/.m2 25 | ~/.gitlibs 26 | key: ${{ runner.os }}-${{ hashFiles('deps.edn') }} 27 | restore-keys: | 28 | ${{ runner.os }}- 29 | - name: Install deps 30 | run: | 31 | clojure -A:check:test:coverage -P 32 | - name: Run check 33 | run: clojure -M:check 34 | - name: Run tests 35 | run: clojure -M:test 36 | - name: Run tests for 1.11 37 | run: clojure -M:test:1.11 38 | - name: Run tests for 1.10 39 | run: clojure -M:test:1.10 40 | - name: Measure test coverage 41 | run: clojure -M:test:coverage 42 | - name: Upload coverage report to CodeCov 43 | uses: codecov/codecov-action@v5 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | profiles.clj 5 | /pom.xml 6 | /pom.xml.asc 7 | *.jar 8 | *.class 9 | /.cpcache 10 | /.lein-* 11 | /.nrepl-port 12 | /.prepl-port 13 | .hgignore 14 | .hg/ 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | 6 | ## [0.3.0] - 2025-05-29 7 | ### Added 8 | - [#9](https://github.com/athos/sweet-array/pull/9) Support for array class syntax as alternative for array type descriptors 9 | - [#16](https://github.com/athos/sweet-array/pull/16) Experimental support for array literals 10 | ### Changed 11 | - [#13](https://github.com/athos/sweet-array/pull/13) Use array class syntax for array type names in error messages if it's supported 12 | 13 | ## [0.2.0] - 2023-09-06 14 | ### Added 15 | - [#1](https://github.com/athos/sweet-array/pull/1) Add `def` macro 16 | - [#3](https://github.com/athos/sweet-array/pull/3) Add `array?` / `array-type?` 17 | - [#6](https://github.com/athos/sweet-array/pull/6) Support keywords to denote primitive types in array type descriptors 18 | 19 | ## [0.1.0] - 2021-11-23 20 | - First release 21 | 22 | [Unreleased]: https://github.com/athos/sweet-array/compare/0.3.0...HEAD 23 | [0.3.0]: https://github.com/athos/sweet-array/compare/0.2.0...0.3.0 24 | [0.2.0]: https://github.com/athos/sweet-array/compare/0.1.0...0.2.0 25 | [0.1.0]: https://github.com/athos/sweet-array/releases/tag/0.1.0 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public 267 | License as published by the Free Software Foundation, either version 2 268 | of the License, or (at your option) any later version, with the GNU 269 | Classpath Exception which is available at 270 | https://www.gnu.org/software/classpath/license.html." 271 | 272 | Simply including a copy of this Agreement, including this Exhibit A 273 | is not sufficient to license the Source Code under Secondary Licenses. 274 | 275 | If it is not possible or desirable to put the notice in a particular 276 | file, then You may include the notice in a location (such as a LICENSE 277 | file in a relevant directory) where a recipient would be likely to 278 | look for such a notice. 279 | 280 | You may add additional accurate notices of copyright ownership. 281 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sweet-array 2 | [![Clojars Project](https://img.shields.io/clojars/v/dev.athos/sweet-array.svg)](https://clojars.org/dev.athos/sweet-array) 3 | ![build](https://github.com/athos/sweet-array/workflows/build/badge.svg) 4 | [![codecov](https://codecov.io/gh/athos/sweet-array/branch/main/graph/badge.svg?token=phoLI2vS9n)](https://codecov.io/gh/athos/sweet-array) 5 | 6 | Array manipulation library for Clojure with "sweet" array type notation and more safety by static types 7 | 8 | ## Table of Contents 9 | 10 | - [Rationale](#rationale) 11 | - [Installation](#installation) 12 | - [Usage](#usage) 13 | - [Array creation](#array-creation) 14 | - [Array definition](#array-definition) 15 | - [Array indexing](#array-indexing) 16 | - [Type-related utilities](#type-related-utilities) 17 | - [Array literals](#array-literals) 18 | - [Array type notation](#array-type-notation) 19 | 20 | ## Rationale 21 | 22 | Clojure has built-in support for Java arrays and provides a set of 23 | facilities for manipulating them, including `make-array`, `aget`, `aset` and so on. 24 | However, some of their difficulties like the following tend to lead users to 25 | write verbose or unexpectedly inefficient code: 26 | 27 | - Need to use different constructor functions for different types and dimensionalities 28 | - Clojure compiler sometimes does not know the static type of arrays and may emit inefficient bytecode (especially for multi-dimensional arrays) 29 | - Array type hints tend to be cryptic (e.g. `[[D`, `[Ljava.lang.String;`, etc.) and can occasionally be quite hard for humans to write manually 30 | - Array class syntax introduced in Clojure 1.12 have mitigated this issue to some extent 31 | 32 | These issues have been pointed out by various Clojurians out there in the past: 33 | 34 | - [Taming multidim Arrays](http://clj-me.cgrand.net/2009/10/15/multidim-arrays/) by Christophe Grand (@cgrand) 35 | - [Java arrays and unchecked math](http://clojure-goes-fast.com/blog/java-arrays-and-unchecked-math/) by Clojure Goes Fast 36 | - [CLJ-1289: aset-* and aget perform poorly on multi-dimensional arrays even with type hints](https://clojure.atlassian.net/browse/CLJ-1288) 37 | 38 | `sweet-array` aims to provide solutions for them. Contretely: 39 | 40 | - It defines *array type descriptors*, a concise and intuitive array type notation, and provides a generic array constructor which can be used for any types and dimensionalities 41 | - The array constructors in the library maintain the static type of arrays, which reduces the cases where users have to add type hints manually 42 | - The array operators in the library automatically infer the resulting array type, so even multi-dimensional arrays can be handled efficiently 43 | 44 | As a result, we can write code like the following using `sweet-array`: 45 | 46 | ```clojure 47 | ;; Example of multiplying two arrays as matrices 48 | 49 | (require '[sweet-array.core :as sa]) 50 | 51 | (sa/def a (sa/new [[double]] [[1.0 2.0] [3.0 4.0]])) 52 | (sa/def b (sa/new [[double]] [[5.0 6.0] [7.0 8.0]])) 53 | 54 | ;; Or, you can write it like this: 55 | ;; (sa/def a (sa/new double/2 [[1.0 2.0] [3.0 4.0]])) 56 | ;; (sa/def b (sa/new double/2 [[5.0 6.0] [7.0 8.0]])) 57 | 58 | (let [nrows (alength a) 59 | ncols (alength (sa/aget b 0)) 60 | n (alength b) 61 | c (sa/new [[double]] nrows ncols)] 62 | (dotimes [i nrows] 63 | (dotimes [j ncols] 64 | (dotimes [k n] 65 | (sa/aset c i j 66 | (+ (* (sa/aget a i k) 67 | (sa/aget b k j)) 68 | (sa/aget c i j)))))) 69 | c) 70 | ``` 71 | 72 | Instead of: 73 | 74 | ```clojure 75 | (def ^double/2 a (into-array [(double-array [1.0 2.0]) (double-array [3.0 4.0])])) 76 | (def ^double/2 b (into-array [(double-array [5.0 6.0]) (double-array [7.0 8.0])])) 77 | 78 | (let [nrows (alength a) 79 | ncols (alength ^double/1 (aget b 0)) 80 | n (alength b) 81 | ^double/2 c (make-array Double/TYPE nrows ncols)] 82 | (dotimes [i nrows] 83 | (dotimes [j ncols] 84 | (dotimes [k n] 85 | (aset ^double/1 (aget c i) j 86 | (+ (* (aget ^double/1 (aget a i) k) 87 | (aget ^double/1 (aget b k) j)) 88 | (aget ^double/1 (aget c i) j)))))) 89 | c) 90 | ``` 91 | 92 | Note that all the type hints in this code are mandatory to make it run as fast as the above one with `sweet-array`. 93 | 94 | ## Installation 95 | 96 | Add the following to your `deps.edn` / `project.clj`: 97 | 98 | - `deps.edn` 99 | ``` 100 | {dev.athos/sweet-array {:mvn/version "0.2.0"}} 101 | ``` 102 | 103 | - `project.clj` 104 | ``` 105 | [dev.athos/sweet-array "0.2.0"] 106 | ``` 107 | 108 | ## Usage 109 | 110 | ### Array creation 111 | 112 | #### `(new [T] n1 n2 ... nk)` 113 | 114 | The simplest way to create an array using this library is to use 115 | the `sweet-array.core/new` macro. The `new` macro is a generic array constructor 116 | which can be used to create both primitive and reference type arrays: 117 | 118 | ```clojure 119 | (require '[sweet-array.core :as sa]) 120 | 121 | (def xs (sa/new [int] 3)) 122 | (class xs) ;=> int/1, which means int array type 123 | (alength xs) ;=> 3 124 | 125 | (def ys (sa/new [String] 5)) 126 | (class ys) ;=> java.lang.String/1, which means String array type 127 | (alength ys) ;=> 5 128 | ``` 129 | 130 | The first argument of the `new` macro is what we call an *array type descriptor*. 131 | See the [Array type notation](#array-type-notation) section for more details, but roughly speaking, 132 | an array type descriptor `[T]` denotes an array type whose component type is `T` 133 | (e.g. `[int]` denotes the int array type and `[String]` denotes the String array type). 134 | 135 | The `new` macro can also be used to create multi-dimensional arrays. 136 | The following example creates a two-dimensional int array: 137 | 138 | ```clojure 139 | (def arr (sa/new [[int]] 2 3)) 140 | (class arr) ;=> int/2, which means 2-d int array type 141 | (alength arr) ;=> 2 142 | (alength (aget arr 0)) ;=> 3 143 | ``` 144 | 145 | In general, `(sa/new [[T]] n1 n2)` produces a 2-d array of type `T` of size `n1`x`n2` 146 | and `(sa/new [[[T]]] n1 n2 n3)` produces a 3-d array of type `T` of size `n1`x`n2`x`n3`, 147 | and so on. 148 | 149 | > [!NOTE] 150 | > For primitive types, `T` can also be represented as a keyword instead of a bare symbol 151 | > (as in `[:int]` or `[[:double]]`). This is useful to avoid issues with automatic 152 | > namespace qualification in the syntax quote and false alerts reported by the linter. 153 | 154 | Since 0.3.0, [array class syntax](https://github.com/clojure/clojure/blob/master/changes.md#27-array-class-syntax) introduced in Clojure 1.12 can be used interchangeably with array type descriptors anywhere within the library: 155 | 156 | ```clojure 157 | (def arr1 (sa/new String/1 5)) 158 | (class arr1) ;=> java.lang.String/1 159 | 160 | (def arr2 (sa/new int/2 2 3)) 161 | (class arr2) ;=> int/2 162 | ``` 163 | 164 | #### `(new [T] [e1 e2 ... ek])` 165 | 166 | The `new` macro provides another syntax to create an array enumerating 167 | the initial elements. `(sa/new [T] [e1 e2 ... ek])` creates an array 168 | initialized with the elements `e1`, `e2`, ..., `ek`: 169 | 170 | ```clojure 171 | (def arr (sa/new [int] [1 2 3])) 172 | (alength arr) ;=> 3 173 | [(aget arr 0) (aget arr 1) (aget arr 2)] ;=> [1 2 3] 174 | ``` 175 | 176 | In general, `(sa/new [T] [e1 e2 ... ek])` is equivalent to: 177 | 178 | ```clojure 179 | (doto (sa/new [T] k) 180 | (aset 0 e1) 181 | (aset 1 e2) 182 | ... 183 | (aset (- k 1) ek)) 184 | ``` 185 | 186 | This form can be used to initialize arrays of any dimensionality: 187 | 188 | ```clojure 189 | ;; 2-d double array 190 | (sa/new [[double]] [[1.0 2.0] [3.0 4.0]]) 191 | ;; 3-d boolean array 192 | (sa/new [[[boolean]]] 193 | [[[true false] [false true]] 194 | [[false true] [true false]]] 195 | ``` 196 | 197 | When initializing multi-dimensional arrays, the init expression for each element 198 | may itself be an array or an expression that evaluates to an array: 199 | 200 | ```clojure 201 | (def xs (sa/new [double] [1.0 2.0])) 202 | (def ys (sa/new [double] [3.0 4.0])) 203 | (sa/new [[double]] [xs ys]) 204 | ``` 205 | 206 | #### `(into-array [T] coll)` 207 | 208 | Another way to create an array is to use the `sweet-array.core/into-array` macro: 209 | 210 | ```clojure 211 | (require '[sweet-array.core :as sa]) 212 | 213 | (def arr (sa/into-array [int] (range 10))) 214 | (class arr) ;=> int/1 215 | (alength arr) ;=> 10 216 | ``` 217 | 218 | Like `clojure.core/into-array`, `sa/into-array` converts an existing collection 219 | (Seqable) into an array. Unlike `clojure.core/into-array`, the resulting array 220 | type is specified with the [array type descriptor](#array-type-notation) as the first argument. 221 | 222 | `sa/into-array` can also be used to create multi-dimensional arrays: 223 | 224 | ```clojure 225 | (def arr' (sa/into-array [[int]] (partition 2 (range 10)))) 226 | (class arr') ;=> int/2 227 | [(aget arr' 0 0) (aget arr' 0 1) (aget arr' 1 0) (aget arr' 1 1)] 228 | ;=> [0 1 2 3] 229 | ``` 230 | 231 | #### `(into-array [T] xform coll)` 232 | 233 | The `sa/into-array` macro optionally takes a [transducer](https://clojure.org/reference/transducers). 234 | This form is inspired by and therefore analogous to `(into to xform from)`. 235 | That is, the transducer `xform` as the second argument will be applied 236 | while converting the collection into an array: 237 | 238 | ```clojure 239 | (def arr (sa/into-array [int] (filter even?) (range 10))) 240 | (alength arr) ;=> 5 241 | [(aget arr 0) (aget arr 1) (aget arr 2)] ;=> [0 2 4] 242 | ``` 243 | 244 | This is especially useful to do transformations that increase or decrease 245 | the dimensionality of an array: 246 | 247 | ```clojure 248 | ;; 1-d to 2-d conversion 249 | (sa/into-array [[int]] (partition-all 2) (sa/new [int] [1 2 3 4])) 250 | 251 | ;; 2-d to 1-d conversion 252 | (sa/into-array [double] cat (sa/new [[double]] [[1.0 2.0] [3.0 4.0]])) 253 | ``` 254 | 255 | ### Array definition 256 | 257 | #### `(def name init)` 258 | #### `(def name docstring init)` 259 | 260 | Since 0.2.0, `sweet-array` provides its own version of the `def` macro. 261 | It can be used as a drop-in replacement of Clojure's `def`. Unlike the ordinary 262 | `def` form, it infers the static type of `init` and implicitly adds the inferred 263 | Using the `def` macro together with other macros from this library, it's hardly 264 | necessary to add a type hint explicitly: 265 | 266 | ```clojure 267 | (sa/def arr (sa/new [int] [1 2 3])) 268 | 269 | (:tag (meta #arr)) 270 | ;=> [I 271 | ``` 272 | 273 | Note that the `def` macro will throw an error at expansion time if the type of 274 | the `init` expression cannot be inferred or the inferred type is not an array 275 | type: 276 | 277 | ```clojure 278 | (sa/def arr (identity (sa/new [int] [1 2 3]))) 279 | ;; Syntax error macroexpanding sweet-array.core/def* at (REPL:1:1). 280 | ;; Can't infer the static type of (identity (sa/new [int] [1 2 3])). Use `sweet-array.core/cast` to explicitly specify the array type or use `def` instead. 281 | 282 | (sa/def arr 42) 283 | ;; Syntax error macroexpanding sweet-array.core/def* at (REPL:1:1). 284 | ;; Can't use sweet-array.core/def for 42, which is long, not array 285 | ``` 286 | 287 | ### Array indexing 288 | 289 | #### `(aget array idx1 idx2 ... idxk)` 290 | #### `(aset array idx1 idx2 ... idxk val)` 291 | 292 | `sweet-array` provides its own version of `aget` / `aset` for indexing arrays. 293 | They work almost the same way as `aget` / `aset` defined in `clojure.core`: 294 | 295 | ```clojure 296 | (require '[sweet-array.core :as sa]) 297 | 298 | (sa/def arr (sa/new [int] [1 2 3 4 5])) 299 | 300 | (sa/aget arr 2) ;=> 3 301 | (sa/aset arr 2 42) 302 | (sa/aget arr 2) ;=> 42 303 | ``` 304 | 305 | Of course, they can also be used for multi-dimensional arrays as 306 | `c.c/aget` & `aset`: 307 | 308 | ```clojure 309 | (sa/def arr (sa/new [double] [[1.0 2.0] [3.0 4.0]])) 310 | 311 | (sa/aget arr 1 1) ;=> 4.0 312 | (sa/aset arr 1 1 42) 313 | (sa/aget arr 1 1) ;=> 42 314 | ``` 315 | 316 | The difference is that `sa/aget` and `sa/aset` infer the static type of their 317 | first argument and utilize it for several purposes as follows. 318 | In a nutshell, they are safer and faster: 319 | 320 | - Static type checking for the array argument 321 | - If the type inference fails, they will fall back to `c.c/aget` & `aset` and emit an reflection warning 322 | ```clojure 323 | (set! *warn-on-reflection* true) 324 | 325 | (fn [arr] (sa/aget arr 0)) 326 | ;; Reflection warning, ... - call to static method aget on clojure.lang.RT can't be resolved (argument types: unknown, int). 327 | 328 | (fn [arr] (sa/aget arr 0 0)) 329 | ;; Reflection warning, ... - type of first argument for aget cannot be inferred 330 | ``` 331 | - If the type inference succeeds but the inferred type of the first argument is not an array type, then they will raise a compile-time error 332 | ```clojure 333 | (sa/aget "I'm a string" 0) 334 | ;; Syntax error macroexpanding sweet-array.core/aget* at ... 335 | ;; Can't apply aget to "I'm a string", which is java.lang.String, not array 336 | ``` 337 | - If more indices are passed to them than the inferred array type expects, then they will raise a compile-time error 338 | ```clojure 339 | (sa/aget (sa/new [int] 3) 0 1 2) 340 | ;; Syntax error macroexpanding sweet-array.core/aget* at ... 341 | ;; Can't apply aget to (sa/new [int] 3) with more than 1 index(es) 342 | ``` 343 | - Faster access to multi-dimensional arrays by automatic type hint insertion 344 | - `sa/aget` & `sa/aset` know that indexing `[T]` once results in the type `T`, and automatically insert obvious type hints to the expanded form, which reduces the cases where one has to add type hints manually 345 | ```clojure 346 | (require '[criterium.core :as cr]) 347 | 348 | (sa/def arr 349 | (sa/into-array [[int]] (map (fn [i] (map (fn [j] (* i j)) (range 10))) (range 10))) 350 | 351 | (cr/quick-bench (dotimes [i 10] (dotimes [j 10] (aget arr i j)))) 352 | ;; Evaluation count : 792 in 6 samples of 132 calls. 353 | ;; Execution time mean : 910.441562 µs 354 | ;; Execution time std-deviation : 170.924552 µs 355 | ;; Execution time lower quantile : 758.037129 µs ( 2.5%) 356 | ;; Execution time upper quantile : 1.151744 ms (97.5%) 357 | ;; Overhead used : 8.143474 ns 358 | 359 | ;; The above result is way too slow due to unrecognizable reflection 360 | ;; To avoid this slowness, you'll need to add type hints yourself 361 | 362 | (cr/quick-bench (dotimes [i 10] (dotimes [j 10] (aget ^ints (aget arr i) j)))) 363 | ;; Evaluation count : 4122636 in 6 samples of 687106 calls. 364 | ;; Execution time mean : 139.098679 ns 365 | ;; Execution time std-deviation : 2.387043 ns 366 | ;; Execution time lower quantile : 136.235737 ns ( 2.5%) 367 | ;; Execution time upper quantile : 142.183007 ns (97.5%) 368 | ;; Overhead used : 8.143474 ns 369 | 370 | ;; Using `sa/aget`, you can simply write as follows: 371 | 372 | (cr/quick-bench (dotimes [i 10] (dotimes [j 10] (sa/aget arr i j)))) 373 | ;; Evaluation count : 5000448 in 6 samples of 833408 calls. 374 | ;; Execution time mean : 113.195074 ns 375 | ;; Execution time std-deviation : 4.641354 ns 376 | ;; Execution time lower quantile : 108.656324 ns ( 2.5%) 377 | ;; Execution time upper quantile : 119.427431 ns (97.5%) 378 | ;; Overhead used : 8.143474 ns 379 | ``` 380 | 381 | 382 | ### Type-related utilities 383 | 384 | `sweet-array` also provides several utilities that are useful for dealing with 385 | array types. 386 | 387 | #### `(type [T])` 388 | 389 | The `sweet-array.core/type` macro is convenient to reify an array type object 390 | represented with an [array type descriptor](#array-type-notation): 391 | 392 | ```clojure 393 | (require '[sweet-array.core :as sa]) 394 | 395 | (sa/type [int]) ;=> int/1 396 | (sa/type [String]) ;=> java.lang.String/1 397 | (sa/type [[double]]) ;=> double/2 398 | ``` 399 | 400 | > [!NOTE] 401 | > It is recommended to use the array class syntax instead of the `type` macro 402 | > when using Clojure 1.12 or later. 403 | 404 | #### `(instance? [T] expr)` 405 | 406 | The `sweet-array.core/instance?` macro is a predicate to check if a given value is 407 | of the specified array type: 408 | 409 | ```clojure 410 | (sa/instance? [int] (sa/new [int] [1 2 3])) ;=> true 411 | (sa/instance? [Object] (sa/new [int] [1 2 3])) ;=> false 412 | (sa/instance? [String] "foo") ;=> false 413 | ``` 414 | 415 | `(sa/instance? [T] expr)` is just syntactic sugar for `(instance? (sa/type [T]) expr)`. 416 | 417 | #### `(cast [T] expr)` 418 | 419 | The `sweet-array.core/cast` macro is for coercing an expression to the specified 420 | array type. It's useful for resolving reflection warnings when some expression 421 | cannot be type-inferred: 422 | 423 | ```clojure 424 | (defn make-array [n] (sa/new [int] n)) 425 | 426 | (set! *warn-on-reflection* true) 427 | 428 | (sa/aget (make-array 3) 0) 429 | ;; Reflection warning, ... - call to static method aget on clojure.lang.RT can't be resolved (argument types: unknown, int). 430 | ;=> 0 431 | 432 | (sa/aget (sa/cast [int] (make-array 3)) 0) 433 | ;=> 0 434 | ``` 435 | 436 | Note that `sa/cast` only has the compile-time effect, and does nothing else at runtime. 437 | 438 | #### `#sweet/tag [T]` 439 | 440 | For those who want to radically eliminate cryptic array type hints (e.g. `^"[I"` 441 | and `^"[Ljava.lang.String;"`) from your code, `sweet-array` provides reader syntax 442 | that can be used as a replacement for them. 443 | 444 | By prefixing `#sweet/tag`, you can write an array type descriptor as a type hint: 445 | 446 | ```clojure 447 | (defn ^#sweet/tag [String] select-randomly [^#sweet/tag [[String]] arr] 448 | (sa/aget arr (rand-int (alength arr)))) 449 | ``` 450 | 451 | This code compiles without any reflection warning, just as with: 452 | 453 | ```clojure 454 | (defn ^"[Ljava.lang.String;" select-randomly [^"[[Ljava.lang.String;" arr] 455 | (sa/aget arr (rand-int (alength arr)))) 456 | ``` 457 | 458 | > [!NOTE] 459 | > It is recommended to use the array class syntax instead of the `#sweet/tag` 460 | > type hint when using Clojure 1.12 or later. 461 | 462 | ### Array literals 463 | 464 | Since 0.3.0, `sweet-array` provides experimental support for *array literals* 465 | (This feature requires Clojure 1.12 or later). 466 | 467 | Array literals allow you to create one- or multi-dimensional arrays in a concise 468 | and readable way. Once enabled, you can write Java arrays of any type and dimensionality 469 | using tagged literals like: 470 | 471 | ```clojure 472 | #int/1 [1 2 3] 473 | ;; equivalent to (sa/new int/1 [1 2 3]) 474 | 475 | #double/2 [[1.0 0.0] [0.0 1.0]] 476 | ;; equivalent to (sa/new double/2 [[1.0 0.0] [0.0 1.0]]) 477 | 478 | #String/1 ["foo" "bar"] 479 | ;; equivalent to (sa/new String/1 ["foo" "bar"]) 480 | ``` 481 | 482 | There are two ways to enable array literals, depending on the context in which you're using them. 483 | 484 | To enable array literals throughout your project or application code, call 485 | `sweet-array.core/install-array-literals!` from your `user.clj`: 486 | 487 | ```clojure 488 | ;; user.clj 489 | (require '[sweet-array.core :as sa]) 490 | 491 | (sa/install-array-literals!) 492 | ``` 493 | 494 | Note that this function has no effect when called from the REPL. To experiment 495 | with array literals during a REPL session, call `sweet-array.core/use-array-literals!` 496 | instead. 497 | 498 | This temporarily enables array literal support only for the current REPL session, 499 | even if `sweet-array.core/install-array-literals!` was not used in `user.clj`: 500 | 501 | ```clojure 502 | user=> (require '[sweet-array.core :as sa]) 503 | user=> (sa/use-array-literals!) 504 | user=> (sa/array? #int/1 [10 20 30]) ;=> true 505 | ``` 506 | 507 | ## Array type notation 508 | 509 | `sweet-array` adopts what we call *array type descriptors* to denote array types 510 | throughout the library. Following is the definition of `sweet-array`'s 511 | array type descriptors: 512 | 513 | ``` 514 | ::= '[' + + ']' 515 | | 516 | | 517 | 518 | ::= 519 | | 520 | | 521 | 522 | ::= 523 | | 524 | 525 | ::= 'boolean' 526 | | 'byte' 527 | | 'char' 528 | | 'short' 529 | | 'int' 530 | | 'long' 531 | | 'float' 532 | | 'double' 533 | 534 | ::= ':' + 535 | 536 | ::= any valid class or interface name 537 | 538 | ::= 539 | | 540 | 541 | ::= 'booleans' 542 | | 'bytes' 543 | | 'shorts' 544 | | 'ints' 545 | | 'longs' 546 | | 'floats' 547 | | 'doubles' 548 | | 'objects' 549 | 550 | ::= ':' + 551 | 552 | ::= + '/' + 553 | | + '/' + 554 | 555 | ::= '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 556 | ``` 557 | 558 | An array type descriptor `[T]` denotes an array whose component type is `T`. 559 | The component type itself may be an array type. For instance, `[[T]]` denotes 560 | the two-dimensional array type of `T`, `[[[T]]]` denotes the three-dimensional 561 | array type of `T`, and so on. 562 | 563 | Both array type aliases (e.g. `ints` and `doubles`) and array class syntax introduced 564 | in Clojure 1.12 (e.g. `int/1` and `String/2`) may also be used as array type 565 | descriptors. They are completely interchangeable with their corresponding array type 566 | descriptor: `ints` and `int/1` are equivalent to `[int]`, and `[doubles]` and 567 | `double/2` are equivalent to `[[double]]`, and so on. 568 | 569 | ## License 570 | 571 | Copyright © 2021 Shogo Ohta 572 | 573 | This program and the accompanying materials are made available under the 574 | terms of the Eclipse Public License 2.0 which is available at 575 | http://www.eclipse.org/legal/epl-2.0. 576 | 577 | This Source Code may also be made available under the following Secondary 578 | Licenses when the conditions for such availability set forth in the Eclipse 579 | Public License, v. 2.0 are satisfied: GNU General Public License as published by 580 | the Free Software Foundation, either version 2 of the License, or (at your 581 | option) any later version, with the GNU Classpath Exception which is available 582 | at https://www.gnu.org/software/classpath/license.html. 583 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b] 3 | [org.corfield.build :as bb])) 4 | 5 | (def lib 'dev.athos/sweet-array) 6 | (def version "0.3.0") 7 | (def tag (b/git-process {:git-args "rev-parse HEAD"})) 8 | 9 | (defn clean [opts] 10 | (bb/clean opts)) 11 | 12 | (defn jar [opts] 13 | (-> opts 14 | (assoc :src-pom "template/pom.xml" 15 | :lib lib :version version :scm {:tag tag}) 16 | (clean) 17 | (bb/jar))) 18 | 19 | (defn install [opts] 20 | (-> opts 21 | (assoc :lib lib :version version) 22 | (bb/install))) 23 | 24 | (defn deploy [opts] 25 | (-> opts 26 | (assoc :lib lib :version version) 27 | (bb/deploy))) 28 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.12.0"} 3 | dev.athos/type-infer {:mvn/version "0.1.2"}} 4 | :aliases 5 | {:1.10 {:extra-deps {org.clojure/clojure {:mvn/version "1.10.3"}}} 6 | :1.11 {:extra-deps {org.clojure/clojure {:mvn/version "1.11.4"}}} 7 | :check 8 | {:extra-deps 9 | {io.github.athos/clj-check {:git/tag "0.1.0" :git/sha "0ca84df"}} 10 | :main-opts ["-m" "clj-check.check"]} 11 | :test 12 | {:extra-paths ["test"] 13 | :extra-deps 14 | {io.github.cognitect-labs/test-runner 15 | {:git/tag "v0.5.1" :git/sha "dfb30dd"}} 16 | :main-opts ["-m" "cognitect.test-runner"] 17 | :exec-fn cognitect.test-runner.api/test} 18 | :coverage 19 | {:extra-deps {cloverage/cloverage {:mvn/version "1.2.4"}} 20 | :main-opts ["-m" "cloverage.coverage" "-p" "src" "-s" "test" "--codecov"]} 21 | :build 22 | {:deps 23 | {io.github.clojure/tools.build {:git/tag "v0.10.6" :git/sha "52cf7d6"} 24 | io.github.seancorfield/build-clj {:git/tag "v0.9.2" :git/sha "9c9f078"}} 25 | :ns-default build}}} 26 | -------------------------------------------------------------------------------- /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {sweet/tag sweet-array.core/tag-fn 2 | sweet/type sweet-array.core/type-fn} 3 | -------------------------------------------------------------------------------- /src/sweet_array/core.clj: -------------------------------------------------------------------------------- 1 | (ns sweet-array.core 2 | (:refer-clojure :exclude [aclone aget aset cast instance? into-array type]) 3 | (:require [clojure.core :as c] 4 | [type-infer.core :as ty]) 5 | (:import [clojure.lang RT] 6 | [java.lang.reflect Array])) 7 | 8 | (def array-class-syntax-supported? 9 | (try 10 | (and (read-string "int/1") true) 11 | (catch Exception _ false))) 12 | 13 | (defn- type->tag [^Class type] 14 | (.getName type)) 15 | 16 | (defn- tag->type [tag] 17 | (Class/forName tag)) 18 | 19 | (def ^:private array-type-tags 20 | {"booleans" "[Z", "bytes" "[B", "chars" "[C" 21 | "shorts" "[S", "ints" "[I", "longs" "[J" 22 | "floats" "[F", "doubles" "[D" 23 | "objects" "[Ljava.lang.Object;"}) 24 | 25 | (defn tag-fn [type-desc] 26 | (letfn [(error! [] 27 | (throw 28 | (ex-info (str "Invalid array type descriptor: " (pr-str type-desc)) 29 | {:descriptor type-desc}))) 30 | (step [desc toplevel?] 31 | (cond (vector? desc) 32 | (if (= (count desc) 1) 33 | (str \[ (step (first desc) false)) 34 | (error!)) 35 | 36 | (ident? desc) 37 | (or (when (nil? (namespace desc)) 38 | (let [desc' (name desc) 39 | t (case desc' 40 | "boolean" "Z" "byte" "B" "char" "C" "short" "S" 41 | "int" "I" "long" "J" "float" "F" "double" "D" 42 | nil)] 43 | (if t 44 | (if toplevel? 45 | (error!) 46 | t) 47 | (array-type-tags desc')))) 48 | (when-let [ret (and (symbol? desc) (resolve desc))] 49 | (when (class? ret) 50 | (if (.isArray ^Class ret) 51 | (.getName ^Class ret) 52 | (when-not toplevel? 53 | (str \L (.getName ^Class ret) \;))))) 54 | (error!)) 55 | 56 | :else (error!)))] 57 | (step type-desc true))) 58 | 59 | (defmacro tag [desc] 60 | (tag-fn desc)) 61 | 62 | (defn type-fn ^Class [desc] 63 | (tag->type (tag-fn desc))) 64 | 65 | (defmacro type 66 | "Returns the class object that represents the array type denoted by type-desc." 67 | [desc] 68 | (type-fn desc)) 69 | 70 | (defmacro instance? 71 | "Evaluates x and tests if it is an instance of the array type denoted by 72 | type-desc." 73 | [type-desc x] 74 | `(c/instance? ~(type-fn type-desc) ~x)) 75 | 76 | (defn array-type? 77 | "Returns true if and only if the given type is an array type." 78 | {:added "0.2.0"} 79 | [^Class t] 80 | (and (not (nil? t)) (.isArray t))) 81 | 82 | (defn array? 83 | "Returns true if and only if the given object is an array." 84 | {:added "0.2.0"} 85 | [x] 86 | (array-type? (class x))) 87 | 88 | (defn- type->str [^Class type] 89 | (if (and array-class-syntax-supported? (array-type? type)) 90 | (pr-str type) 91 | (.getName type))) 92 | 93 | (defn- warn [fmt & vals] 94 | (binding [*out* *err*] 95 | (apply printf fmt vals) 96 | (newline))) 97 | 98 | (defn- rt-aget [arr idx] 99 | `(RT/aget ~arr (unchecked-int ~idx))) 100 | 101 | (defn- rt-aset [arr idx expr] 102 | `(RT/aset ~arr (unchecked-int ~idx) ~expr)) 103 | 104 | (defn- expand-to-macro* [macro* &form arr & args] 105 | (let [m (-> (meta &form) 106 | (assoc ::form &form))] 107 | (if (and (symbol? arr) (nil? (:tag (meta arr)))) 108 | (with-meta `(~macro* ~arr ~@args) m) 109 | (let [asym (gensym 'arr)] 110 | `(let [~asym ~arr] 111 | ~(with-meta 112 | `(~macro* ~asym ~@args) 113 | m)))))) 114 | 115 | (defmacro aget 116 | "Works just like clojure.core/aget, but with static checking of the array type 117 | to detect type errors at compile time." 118 | [arr idx & more] 119 | (apply expand-to-macro* `aget* &form arr idx more)) 120 | 121 | (defmacro aget* [arr idx & more] 122 | (if-let [t (ty/infer-type &env arr)] 123 | (if (not (array-type? t)) 124 | (let [form (::form (meta &form)) 125 | msg (str "Can't apply aget to " 126 | (pr-str (second form)) 127 | ", which is " (type->str t) ", not array")] 128 | (throw (ex-info msg {:form form}))) 129 | (loop [t t, arr arr, idx (cons idx more), n 0] 130 | (if (seq idx) 131 | (if (array-type? t) 132 | (let [ctype (.getComponentType t)] 133 | (recur ctype 134 | (with-meta 135 | (rt-aget arr (first idx)) 136 | {:tag (type->tag ctype)}) 137 | (rest idx) 138 | (inc n))) 139 | (let [form (::form (meta &form)) 140 | msg (str "Can't apply aget to " 141 | (pr-str (second form)) 142 | " with more than " n " index(es)")] 143 | (throw (ex-info msg {:form form})))) 144 | arr))) 145 | (do 146 | (when (and *warn-on-reflection* (seq more)) 147 | (let [{:keys [line column]} (meta &form)] 148 | (warn "Reflection warning, %s:%d:%d - type of first argument for aget cannot be inferred" 149 | *file* line column))) 150 | `(c/aget ~arr ~idx ~@more)))) 151 | 152 | (def ^:private primitive-coerce-fns 153 | {Boolean/TYPE `boolean 154 | Byte/TYPE `unchecked-byte 155 | Character/TYPE `unchecked-char 156 | Short/TYPE `unchecked-short 157 | Integer/TYPE `unchecked-int 158 | Long/TYPE `unchecked-long 159 | Float/TYPE `unchecked-float 160 | Double/TYPE `unchecked-double}) 161 | 162 | (defmacro aset 163 | "Works just like clojure.core/aset, but with static checking of the array type 164 | to detect type errors at compile time." 165 | [arr idx & idxv] 166 | (apply expand-to-macro* `aset* &form arr idx idxv)) 167 | 168 | (defmacro aset* [arr idx & idxv] 169 | (if-let [t (ty/infer-type &env arr)] 170 | (if (not (array-type? t)) 171 | (let [form (::form (meta &form)) 172 | msg (str "Can't apply aset to " 173 | (pr-str (second form)) 174 | ", which is " (type->str t) ", not array")] 175 | (throw (ex-info msg {:form form}))) 176 | (let [[more v] ((juxt butlast last) idxv) 177 | vtype (loop [t (.getComponentType t), more more, n 1] 178 | (cond (empty? more) t 179 | 180 | (not (.isArray t)) 181 | (let [form (::form (meta &form)) 182 | msg (str "Can't apply aset to " 183 | (pr-str (second form)) 184 | " with more than " n " index(es)")] 185 | (throw (ex-info msg {:form form}))) 186 | 187 | :else (recur (.getComponentType t) (rest more) (inc n)))) 188 | f (primitive-coerce-fns vtype) 189 | expr (cond->> v f (list f))] 190 | (if (seq more) 191 | (rt-aset `(aget ~arr ~idx ~@(butlast more)) (last more) expr) 192 | (rt-aset arr idx expr)))) 193 | (do 194 | (when (and *warn-on-reflection* (> (count idxv) 1)) 195 | (let [{:keys [line column]} (meta &form)] 196 | (warn "Reflection warning, %s:%d:%d - type of first argument for aset cannot be inferred" 197 | *file* line column))) 198 | `(c/aset ~arr ~idx ~@idxv)))) 199 | 200 | (def ^:private array-ctor-fns 201 | {Boolean/TYPE `boolean-array 202 | Byte/TYPE `byte-array 203 | Character/TYPE `char-array 204 | Short/TYPE `short-array 205 | Integer/TYPE `int-array 206 | Long/TYPE `long-array 207 | Float/TYPE `float-array 208 | Double/TYPE `double-array}) 209 | 210 | (defn- array-ctor-form [t size] 211 | (if-let [f (array-ctor-fns t)] 212 | `(~f (unchecked-int ~size)) 213 | `(Array/newInstance ~t (unchecked-int ~size)))) 214 | 215 | (defn- expand-inits [^Class t inits] 216 | (if (array-type? t) 217 | (if (vector? inits) 218 | (let [asym (gensym 'arr) 219 | ctype (.getComponentType t)] 220 | `(let [~asym ~(with-meta 221 | (array-ctor-form ctype (count inits)) 222 | {:tag (type->tag t)})] 223 | ~@(map-indexed 224 | (fn [i init] 225 | (rt-aset asym i (expand-inits ctype init))) 226 | inits) 227 | ~asym)) 228 | inits) 229 | (if-let [f (primitive-coerce-fns t)] 230 | `(~f ~inits) 231 | inits))) 232 | 233 | (defmacro new 234 | "Creates an array of the type denoted by type-desc. 235 | 236 | The macro has two forms: 237 | - (new [T] n): produce an array of type T of size n 238 | - (new [T] [e_1 ... e_n]): produce an array of type T of size n initialized 239 | with elements e_1, ..., e_n." 240 | [type-desc & args] 241 | (let [t (type-fn type-desc)] 242 | (cond (empty? args) 243 | `(sweet-array.core/new ~type-desc 0) 244 | 245 | (vector? (first args)) 246 | (expand-inits t (first args)) 247 | 248 | :else 249 | (loop [t' t args' args n 0] 250 | (if (seq args') 251 | (if (array-type? t') 252 | (recur (.getComponentType t') (rest args') (inc n)) 253 | (throw 254 | (ex-info (str type-desc " can't take more than " 255 | (count args) " index(es)") 256 | {}))) 257 | (with-meta 258 | (if (> n 1) 259 | `(Array/newInstance ~t' (sweet-array.core/new [~'int] [~@args])) 260 | (array-ctor-form t' (first args))) 261 | {:tag (type->tag t)})))))) 262 | 263 | (defmacro aclone [arr] 264 | (expand-to-macro* `aclone* &form arr)) 265 | 266 | (defmacro aclone* [arr] 267 | (if-let [t (ty/infer-type &env arr)] 268 | (if (array-type? t) 269 | (with-meta `(c/aclone ~arr) {:tag (type->tag t)}) 270 | (let [form (::form (meta &form)) 271 | msg (str "Can't apply aclone to " 272 | (pr-str (second form)) 273 | ", which is " (type->str t) ", not array")] 274 | (throw (ex-info msg {:form form})))) 275 | `(c/aclone ~arr))) 276 | 277 | (defmacro cast 278 | "Casts the given expression expr to the array type denoted by type-desc. 279 | 280 | This macro only has the compile-time effect and does nothing at runtime." 281 | [type-desc expr] 282 | (with-meta expr {:tag (tag-fn type-desc)})) 283 | 284 | (defn- into-array-form [t coll] 285 | (if-let [f (array-ctor-fns t)] 286 | `(~f ~coll) 287 | `(c/into-array ~t ~coll))) 288 | 289 | (defn- expand-into-array [^Class type coll] 290 | (if (array-type? type) 291 | (let [ctype (.getComponentType type) 292 | coll-sym (gensym 'coll)] 293 | (into-array-form ctype 294 | `(for [~coll-sym ~coll] 295 | ~(expand-into-array ctype coll-sym)))) 296 | (if-let [f (primitive-coerce-fns type)] 297 | `(~f ~coll) 298 | coll))) 299 | 300 | (defmacro into-array 301 | "Converts the given collection (seqable) to an array of the type denoted by 302 | type-desc. A transducer may be supplied." 303 | ([type-desc coll] 304 | `(into-array ~type-desc nil ~coll)) 305 | ([type-desc xform coll] 306 | (let [t (type-fn type-desc) 307 | ctype (.getComponentType t) 308 | coll (cond->> coll xform (list `sequence xform))] 309 | (with-meta 310 | (if (array-type? ctype) 311 | (expand-into-array t coll) 312 | (into-array-form ctype coll)) 313 | {:tag (tag-fn type-desc)})))) 314 | 315 | (defmacro def 316 | "The macro version of def dedicated to arrays. 317 | This macro can be used as a drop-in replacement for Clojure's def. Unlike 318 | the ordinary def form, (sweet-array.core/def ) infers the static 319 | type of and implicitly adds the inferred type as the type hint for . 320 | Throws an error at macro expansion time if the type of cannot be statically 321 | inferred or if the inferred type is not an array type." 322 | {:added "0.2.0" 323 | :clj-kondo/lint-as 'clj-kondo.lint-as/def-catch-all} 324 | ([name arr] 325 | (with-meta `(sweet-array.core/def ~name nil ~arr) (meta &form))) 326 | ([name docstr arr] 327 | (if (symbol? arr) 328 | (with-meta `(def* ~name ~arr) (meta &form)) 329 | (let [asym (gensym 'init)] 330 | `(let [~asym ~arr] 331 | ~(with-meta 332 | `(def* ~name ~asym ~docstr ~arr) 333 | (meta &form))))))) 334 | 335 | (defmacro def* [name sym docstr expr] 336 | (let [inferred-type (ty/infer-type &env sym) 337 | tag (:tag (meta name)) 338 | ^Class hinted-type (some-> tag ty/resolve-tag)] 339 | (cond (and tag (nil? hinted-type)) 340 | (let [msg (format "Unable to resolve tag: %s in this context" 341 | (pr-str tag))] 342 | (throw (ex-info msg {:hinted-tag tag}))) 343 | 344 | (and (nil? inferred-type) (nil? hinted-type)) 345 | (let [msg (str "Can't infer the static type of " (pr-str expr) ". " 346 | "Use `sweet-array.core/cast` to explicitly specify " 347 | "the array type or use `def` instead.")] 348 | (throw (ex-info msg {}))) 349 | 350 | (and inferred-type (not (array-type? inferred-type))) 351 | (let [msg (str "Can't use sweet-array.core/def for " (pr-str expr) 352 | ", which is " (type->str inferred-type) ", not array")] 353 | (throw (ex-info msg {:inferred-type inferred-type}))) 354 | 355 | (and hinted-type (not (array-type? hinted-type))) 356 | (let [msg (format "Hinted type (%s) is not an array type" 357 | (type->str hinted-type))] 358 | (throw (ex-info msg {:hinted-type hinted-type}))) 359 | 360 | (and hinted-type 361 | inferred-type 362 | (not (.isAssignableFrom hinted-type inferred-type))) 363 | (let [msg (format "Inferred type (%s) is not compatible with hinted type (%s)" 364 | (type->str inferred-type) 365 | (type->str hinted-type))] 366 | (throw (ex-info msg {:hinted-type hinted-type 367 | :inferred-type inferred-type}))) 368 | 369 | :else 370 | `(def ~(vary-meta name assoc :tag (or hinted-type inferred-type)) 371 | ~@(when docstr [docstr]) 372 | ~sym)))) 373 | 374 | (defn- array-literal-reader-fn [default-reader-fn] 375 | (fn [tag val] 376 | (or (when-let [x (resolve tag)] 377 | (when (and (class? x) (.isArray ^Class x)) 378 | `(sweet-array.core/new ~tag ~val))) 379 | (if default-reader-fn 380 | (default-reader-fn tag val) 381 | (throw (ex-info (str "No reader function for tag " tag) {})))))) 382 | 383 | (defn install-array-literals! 384 | "Experimental - Enables array literals globally. 385 | 386 | This function is intended for use in `user.clj` or other initialization code to 387 | enable array literals. Note: This function has no effect when used from the REPL. 388 | Use `use-array-literals!` to enable array literals interactively in the REPL." 389 | {:added "0.3.0"} 390 | [] 391 | (alter-var-root #'*default-data-reader-fn* array-literal-reader-fn) 392 | nil) 393 | 394 | (defn use-array-literals! 395 | "Experimental - Enables array literals for the current REPL session. 396 | 397 | Use this function when working in the REPL. Note: This function will throw an error 398 | if used in `user.clj`. If you need to enable array literals for code written 399 | in files, use `install-array-literals!` instead." 400 | {:added "0.3.0"} 401 | [] 402 | (set! *default-data-reader-fn* (array-literal-reader-fn *default-data-reader-fn*)) 403 | nil) 404 | -------------------------------------------------------------------------------- /template/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | sweet-array 5 | Array manipulation library for Clojure with "sweet" array type notation and more safety by static types 6 | https://github.com/athos/sweet-array 7 | 8 | 9 | EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0 10 | https://www.eclipse.org/legal/epl-2.0/ 11 | 12 | 13 | 14 | 15 | athos 16 | Shogo Ohta 17 | 18 | 19 | 20 | https://github.com/athos/sweet-array 21 | scm:git:git://github.com/athos/sweet-array.git 22 | scm:git:ssh://git@github.com/athos/sweet-array.git 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/sweet_array/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns sweet-array.core-test 2 | (:require [clojure.test :refer [deftest is are]] 3 | [sweet-array.core :as sa] 4 | [type-infer.core :refer [infer]])) 5 | 6 | (defmacro eval-when-array-class-syntax-is-available [& body] 7 | (when @#'sa/array-class-syntax-supported? 8 | `(do ~@body))) 9 | 10 | (defmacro $ [expr-str] 11 | (binding [*default-data-reader-fn* (#'sa/array-literal-reader-fn nil)] 12 | (read-string expr-str))) 13 | 14 | (defn- array-= [arr1 arr2] 15 | (let [c1 (class arr1) 16 | c2 (class arr2)] 17 | (and (.isArray c1) 18 | (.isArray c2) 19 | (= c1 c2) 20 | (= (count arr1) (count arr2)) 21 | (if (.isArray (.getComponentType c1)) 22 | (every? true? (map array-= arr1 arr2)) 23 | (= (seq arr1) (seq arr2)))))) 24 | 25 | (deftest type-test 26 | (is (= (type (boolean-array 0)) 27 | (sa/type [boolean]) 28 | (sa/type [:boolean]) 29 | (sa/type booleans) 30 | (sa/type :booleans))) 31 | (is (= (type (byte-array 0)) 32 | (sa/type [byte]) 33 | (sa/type [:byte]) 34 | (sa/type bytes) 35 | (sa/type :bytes))) 36 | (is (= (type (char-array 0)) 37 | (sa/type [char]) 38 | (sa/type [:char]) 39 | (sa/type chars) 40 | (sa/type :chars))) 41 | (is (= (type (short-array 0)) 42 | (sa/type [short]) 43 | (sa/type [:short]) 44 | (sa/type shorts) 45 | (sa/type :shorts))) 46 | (is (= (type (int-array 0)) 47 | (sa/type [int]) 48 | (sa/type [:int]) 49 | (sa/type ints) 50 | (sa/type :ints))) 51 | (is (= (type (long-array 0)) 52 | (sa/type [long]) 53 | (sa/type [:long]) 54 | (sa/type longs) 55 | (sa/type :longs))) 56 | (is (= (type (float-array 0)) 57 | (sa/type [float]) 58 | (sa/type [:float]) 59 | (sa/type floats) 60 | (sa/type :floats))) 61 | (is (= (type (double-array 0)) 62 | (sa/type [double]) 63 | (sa/type [:double]) 64 | (sa/type doubles) 65 | (sa/type :doubles))) 66 | (is (= (type (object-array 0)) 67 | (sa/type [Object]) 68 | (sa/type objects) 69 | (sa/type :objects))) 70 | (is (= (type (make-array String 0)) 71 | (sa/type [String]))) 72 | (is (= (type (make-array Integer/TYPE 0 0)) 73 | (sa/type [[int]]) 74 | (sa/type [[:int]]) 75 | (sa/type [ints]) 76 | (sa/type [:ints]))) 77 | (is (= (type (make-array Double/TYPE 0 0 0)) 78 | (sa/type [[[double]]]) 79 | (sa/type [[[:double]]]) 80 | (sa/type [[doubles]]) 81 | (sa/type [[:doubles]]))) 82 | (are [desc] (thrown? Throwable (macroexpand `(sa/type ~'desc))) 83 | 42 84 | int 85 | :long 86 | "String" 87 | ["double"] 88 | [int int] 89 | [UnknownClass])) 90 | 91 | (eval-when-array-class-syntax-is-available 92 | (deftest type-with-array-class-syntax-test 93 | (is (= (sa/type [boolean]) 94 | ($ "(sa/type boolean/1)"))) 95 | (is (= (sa/type [byte]) 96 | ($ "(sa/type byte/1)"))) 97 | (is (= (sa/type [[int]]) 98 | ($ "(sa/type int/2)"))) 99 | (is (= (sa/type [Object]) 100 | ($ "(sa/type Object/1)"))) 101 | (is (= (sa/type [[String]]) 102 | ($ "(sa/type java.lang.String/2)"))))) 103 | 104 | (deftest instance?-test 105 | (is (sa/instance? [int] (int-array [1 2 3]))) 106 | (is (sa/instance? [String] (make-array String 0)) ) 107 | (is (sa/instance? [[double]] (make-array Double/TYPE 0 0))) 108 | (is (not (sa/instance? [boolean] true))) 109 | (is (not (sa/instance? [int] (short-array [1 2 3])))) 110 | (is (not (sa/instance? [[long]] (long-array [1 2 3])))) 111 | (is (not (sa/instance? [double] (make-array Double/TYPE 0 0))))) 112 | 113 | (deftest array-type?-test 114 | (is (sa/array-type? (sa/type [String]))) 115 | (is (not (sa/array-type? String))) 116 | (is (not (sa/array-type? nil)))) 117 | 118 | (deftest array?-test 119 | (is (sa/array? (sa/new [int] [1 2 3]))) 120 | (is (not (sa/array? 42))) 121 | (is (not (sa/array? nil)))) 122 | 123 | (deftest cast-test 124 | (let [arr (make-array Integer/TYPE 0) 125 | arr' (sa/cast [int] arr)] 126 | (is (sa/instance? [int] arr)) 127 | (is (nil? (infer arr))) 128 | (is (sa/instance? [int] arr)) 129 | (is (= (sa/type [int]) (infer arr')))) 130 | (let [arr (make-array String 0) 131 | arr' (sa/cast [String] arr)] 132 | (is (sa/instance? [String] arr)) 133 | (is (nil? (infer arr))) 134 | (is (sa/instance? [String] arr')) 135 | (is (= (sa/type [String]) (infer arr')))) 136 | (let [arr (make-array Double/TYPE 0 0) 137 | arr' (sa/cast [[double]] arr)] 138 | (is (sa/instance? [[double]] arr)) 139 | (is (nil? (infer arr))) 140 | (is (sa/instance? [[double]] arr')) 141 | (is (= (sa/type [[double]]) (infer arr'))))) 142 | 143 | (deftest new-test 144 | (let [arr (sa/new [byte])] 145 | (is (sa/instance? [byte] arr)) 146 | (is (= (sa/type [byte]) (infer arr))) 147 | (is (= 0 (alength arr)))) 148 | (let [arr (sa/new [int] 5)] 149 | (is (sa/instance? [int] arr)) 150 | (is (= (sa/type [int]) (infer arr))) 151 | (is (= 5 (alength arr))) 152 | (is (= [0 0 0 0 0] (seq arr)))) 153 | (let [arr (sa/new [String] 3)] 154 | (is (sa/instance? [String] arr)) 155 | (is (= (sa/type [String]) (infer arr))) 156 | (is (= 3 (alength arr))) 157 | (is (= [nil nil nil] (seq arr)))) 158 | (let [arr (sa/new [[long]] 2 3)] 159 | (is (sa/instance? [[long]] arr)) 160 | (is (= (sa/type [[long]]) (infer arr))) 161 | (is (= 2 (alength arr))) 162 | (is (= 3 (alength (aget arr 0)) (alength (aget arr 1)))) 163 | (is (= [0 0 0] (seq (aget arr 0)) (seq (aget arr 1))))) 164 | (let [arr (sa/new [[[double]]] 2 3 4)] 165 | (is (sa/instance? [[[double]]] arr)) 166 | (is (= (sa/type [[[double]]]) (infer arr))) 167 | (is (= 2 (alength arr))) 168 | (is (= 3 (alength (aget arr 0)) (alength (aget arr 1)))) 169 | (is (= 4 170 | (alength (aget arr 0 0)) 171 | (alength (aget arr 0 1)) 172 | (alength (aget arr 0 2)) 173 | (alength (aget arr 1 0)) 174 | (alength (aget arr 1 1)) 175 | (alength (aget arr 1 2)))) 176 | (is (= [0.0 0.0 0.0 0.0] 177 | (seq (aget arr 0 0)) 178 | (seq (aget arr 0 1)) 179 | (seq (aget arr 0 2)) 180 | (seq (aget arr 1 0)) 181 | (seq (aget arr 1 1)) 182 | (seq (aget arr 1 2))))) 183 | (let [arr (sa/new [[int]] 3)] 184 | (is (sa/instance? [[int]] arr)) 185 | (is (= (sa/type [[int]]) (infer arr))) 186 | (is (= 3 (alength arr))) 187 | (is (= [nil nil nil] (seq arr)))) 188 | (let [arr (sa/new [boolean] [true false true])] 189 | (is (sa/instance? [boolean] arr)) 190 | (is (= (sa/type [boolean]) (infer arr))) 191 | (is (= 3 (alength arr))) 192 | (is (= [true false true] (seq arr)))) 193 | (let [arr (sa/new [String] ["foo" "bar" "baz"])] 194 | (is (sa/instance? [String] arr)) 195 | (is (= (sa/type [String]) (infer arr))) 196 | (is (= 3 (alength arr))) 197 | (is (= ["foo" "bar" "baz"] (seq arr)))) 198 | (let [arr (sa/new [[byte]] [[0 1 2] [3 4 5]])] 199 | (is (sa/instance? [[byte]] arr)) 200 | (is (= (sa/type [[byte]]) (infer arr))) 201 | (is (= 2 (alength arr))) 202 | (is (= 3 (alength (aget arr 0)) (alength (aget arr 1)))) 203 | (is (= [0 1 2] (seq (aget arr 0)))) 204 | (is (= [3 4 5] (seq (aget arr 1))))) 205 | (let [arr (sa/new [[[char]]] [[[\a \b \c]] [[\d \e] [\f]]])] 206 | (is (sa/instance? [[[char]]] arr)) 207 | (is (= (sa/type [[[char]]]) (infer arr))) 208 | (is (= 2 (alength arr))) 209 | (is (= 1 (alength (aget arr 0)))) 210 | (is (= 2 (alength (aget arr 1)))) 211 | (is (= 3 (alength (aget arr 0 0)))) 212 | (is (= 2 (alength (aget arr 1 0)))) 213 | (is (= 1 (alength (aget arr 1 1)))) 214 | (is (= [\a \b \c] (seq (aget arr 0 0)))) 215 | (is (= [\d \e] (seq (aget arr 1 0)))) 216 | (is (= [\f] (seq (aget arr 1 1))))) 217 | (let [xs (sa/new [int] [0 1 2]) 218 | ys (sa/new [int] [3 4]) 219 | arr (sa/new [[int]] [xs ys])] 220 | (is (sa/instance? [[int]] arr)) 221 | (is (= (sa/type [[int]]) (infer arr))) 222 | (is (= 2 (alength arr))) 223 | (is (= 3 (alength (aget arr 0)))) 224 | (is (= 2 (alength (aget arr 1)))) 225 | (is (= [0 1 2] (seq (aget arr 0)))) 226 | (is (= [3 4] (seq (aget arr 1))))) 227 | (is (thrown? Throwable (macroexpand `(sa/new [~'int] 2 3))))) 228 | 229 | (eval-when-array-class-syntax-is-available 230 | (deftest new-with-array-class-syntax-test 231 | (are [expr-str expected] 232 | (array-= expected ($ expr-str)) 233 | "(sa/new int/1 [1 2 3])" 234 | (sa/new [int] [1 2 3]) 235 | 236 | "(sa/new String/1 3)" 237 | (sa/new [String] 3) 238 | 239 | "(sa/new double/2 [[0.0 1.0] [1.0 0.0]])" 240 | (sa/new [[double]] [[0.0 1.0] [1.0 0.0]]) 241 | 242 | "(sa/new java.lang.Object/2 2 2)" 243 | (sa/new [[Object]] 2 2) 244 | 245 | "(sa/new char/3 [[[\\a \\b \\c]] [[\\d \\e] [\\f]]])" 246 | (sa/new [[[char]]] [[[\a \b \c]] [[\d \e] [\f]]])))) 247 | 248 | (def ^String s "foobar") 249 | 250 | (deftest aclone-test 251 | (let [arr (int-array [1 2 3]) 252 | arr' (sa/aclone arr)] 253 | (is (sa/instance? [int] arr')) 254 | (is (= (sa/type [int]) (infer arr'))) 255 | (is (not (identical? arr arr'))) 256 | (is (= [1 2 3] (seq arr')))) 257 | (let [arr (sa/new [[boolean]] [[true false] [false true]]) 258 | arr' (sa/aclone arr)] 259 | (is (sa/instance? [[boolean]] arr')) 260 | (is (= (sa/type [[boolean]]) (infer arr'))) 261 | (is (not (identical? arr arr'))) 262 | (is (identical? (aget arr 0) (aget arr' 0))) 263 | (is (identical? (aget arr 1) (aget arr' 1)))) 264 | (is (thrown? Throwable (macroexpand `(sa/aclone s))))) 265 | 266 | (deftest into-array-test 267 | (let [arr (sa/into-array [boolean] (map even? (range 3)))] 268 | (is (sa/instance? [boolean] arr)) 269 | (is (= (sa/type [boolean]) (infer arr))) 270 | (is (= 3 (alength arr))) 271 | (is (= [true false true] (seq arr)))) 272 | (let [arr (sa/into-array [String] (map str (range 3)))] 273 | (is (sa/instance? [String] arr)) 274 | (is (= (sa/type [String]) (infer arr))) 275 | (is (= 3 (alength arr))) 276 | (is (= ["0" "1" "2"] (seq arr)))) 277 | (let [arr (sa/into-array [[double]] (partition 2 (range 0.0 2.0 0.5)))] 278 | (is (sa/instance? [[double]] arr)) 279 | (is (= (sa/type [[double]]) (infer arr))) 280 | (is (= 2 281 | (alength arr) 282 | (alength (aget arr 0)) 283 | (alength (aget arr 1)))) 284 | (is (= [0.0 0.5] (seq (aget arr 0)))) 285 | (is (= [1.0 1.5] (seq (aget arr 1))))) 286 | (let [arr (sa/into-array [[String]] [["foo" "bar"] ["baz"]])] 287 | (is (sa/instance? [[String]] arr)) 288 | (is (= (sa/type [[String]]) (infer arr))) 289 | (is (= 2 (alength arr) (alength (aget arr 0)))) 290 | (is (= 1 (alength (aget arr 1)))) 291 | (is (= ["foo" "bar"] (seq (aget arr 0)))) 292 | (is (= ["baz"] (seq (aget arr 1))))) 293 | (let [arr (sa/into-array [int] (map inc) (range 3))] 294 | (is (sa/instance? [int] arr)) 295 | (is (= (sa/type [int]) (infer arr))) 296 | (is (= 3 (alength arr))) 297 | (is (= [1 2 3] (seq arr)))) 298 | (let [arr (sa/into-array [String] (map str) (range 3))] 299 | (is (sa/instance? [String] arr)) 300 | (is (= (sa/type [String]) (infer arr))) 301 | (is (= 3 (alength arr))) 302 | (is (= ["0" "1" "2"] (seq arr)))) 303 | (let [arr (sa/into-array [[long]] (partition-all 2) (range 6))] 304 | (is (sa/instance? [[long]] arr)) 305 | (is (= (sa/type [[long]]) (infer arr))) 306 | (is (= 3 (alength arr))) 307 | (is (= 2 308 | (alength (aget arr 0)) 309 | (alength (aget arr 1)) 310 | (alength (aget arr 2)))) 311 | (is (= [0 1] (seq (aget arr 0)))) 312 | (is (= [2 3] (seq (aget arr 1)))) 313 | (is (= [4 5] (seq (aget arr 2))))) 314 | (let [arr (sa/into-array [char] cat [[\a \b \c] [\d \e] [\f]])] 315 | (is (sa/instance? [char] arr)) 316 | (is (= (sa/type [char]) (infer arr))) 317 | (is (= 6 (alength arr))) 318 | (is (= [\a \b \c \d \e \f] (seq arr))))) 319 | 320 | (def ^ints arr-with-type-hint 321 | (sa/new [int] 1)) 322 | 323 | (def arr-without-type-hint 324 | (sa/new [[int]] 1 1)) 325 | 326 | (deftest aget-test 327 | (let [arr (sa/new [int] [100 101 102]) 328 | res (sa/aget arr 1)] 329 | (is (= Integer/TYPE (infer res))) 330 | (is (= 101 res))) 331 | (let [arr (sa/new [String] ["foo" "bar" "baz"]) 332 | res (sa/aget arr 0)] 333 | (is (= String (infer res))) 334 | (is (= "foo" res))) 335 | (let [arr (sa/new [[boolean]] [[true false] [false true]]) 336 | res1 (sa/aget arr 0) 337 | res2 (sa/aget arr 0 1)] 338 | (is (= (sa/type [boolean]) (infer res1))) 339 | (is (= [true false] (seq res1))) 340 | (is (= Boolean/TYPE (infer res2))) 341 | (is (= false res2))) 342 | (let [arr (sa/new [[[float]]] 343 | [[[1.0 0.0] [0.0 1.0]] 344 | [[2.0 0.0] [0.0 2.0]] 345 | [[3.0 0.0] [0.0 3.0]]]) 346 | res1 (sa/aget arr 0) 347 | res2 (sa/aget arr 0 0) 348 | res3 (sa/aget arr 0 0 0)] 349 | (is (= (sa/type [[float]]) (infer res1))) 350 | (is (= [[1.0 0.0] [0.0 1.0]] (map seq res1))) 351 | (is (= (sa/type [float]) (infer res2))) 352 | (is (= [1.0 0.0] (seq res2))) 353 | (is (= Float/TYPE (infer res3))) 354 | (is (= 1.0 res3))) 355 | (is (thrown? Throwable (macroexpand `(sa/aget s 0)))) 356 | (is (thrown? Throwable (macroexpand `(sa/aget arr-with-type-hint 0 0)))) 357 | (is (re-find #"^Reflection warning" 358 | (with-out-str 359 | (binding [*warn-on-reflection* true 360 | *err* *out*] 361 | (macroexpand 362 | `(sa/aget arr-without-type-hint 0 0))))))) 363 | 364 | (deftest aset-test 365 | (let [arr (sa/new [int] [1 2 3])] 366 | (sa/aset arr 1 101) 367 | (is (= [1 101 3] (seq arr)))) 368 | (let [arr (sa/new [[boolean]] [[true false] [false true]])] 369 | (sa/aset arr 0 1 true) 370 | (is (= [true true] (seq (aget arr 0)))) 371 | (is (= [false true] (seq (aget arr 1))))) 372 | (let [arr (sa/new [[[char]]] [[[\a \b \c]] [[\d \e] [\f]]])] 373 | (sa/aset arr 1 0 1 \z) 374 | (is (= [\a \b \c] (seq (aget arr 0 0)))) 375 | (is (= [\d \z] (seq (aget arr 1 0)))) 376 | (is (= [\f] (seq (aget arr 1 1))))) 377 | (is (thrown? Throwable (macroexpand `(sa/aset s 0 "foo")))) 378 | (is (thrown? Throwable (macroexpand `(sa/aset arr-with-type-hint 0 0 42)))) 379 | (is (re-find #"^Reflection warning" 380 | (with-out-str 381 | (binding [*warn-on-reflection* true 382 | *err* *out*] 383 | (macroexpand 384 | `(sa/aset arr-without-type-hint 0 0 42))))))) 385 | 386 | (eval-when-array-class-syntax-is-available 387 | (deftest array-literal-test 388 | (are [expr-str expected] 389 | (array-= expected ($ expr-str)) 390 | "#int/1 [1 2 3]" 391 | (sa/new [int] [1 2 3]) 392 | 393 | "#String/1 [\"foo\" \"bar\"]" 394 | (sa/new [String] ["foo" "bar"]) 395 | 396 | "#double/2 [[0.0 1.0] [1.0 0.0]]" 397 | (sa/new [[double]] [[0.0 1.0] [1.0 0.0]]) 398 | 399 | "#java.lang.Object/2 [[\"foo\" \"bar\"] [:foo :bar] ['foo 'bar]]" 400 | (sa/new [[Object]] [["foo" "bar"] [:foo :bar] ['foo 'bar]]) 401 | 402 | "#char/3 [[[\\a \\b \\c]] [[\\d \\e] [\\f]]]" 403 | (sa/new [[[char]]] [[[\a \b \c]] [[\d \e] [\f]]])))) 404 | 405 | (sa/def arr1 (int-array [1 2 3])) 406 | (sa/def arr2 (sa/into-array [[double]] (partition-all 2) (range 4))) 407 | (sa/def arr3 (sa/cast [String] (into-array String ["foo"]))) 408 | (sa/def ^ints arr4 (into-array Integer/TYPE [1 2 3])) 409 | (sa/def ^#sweet/tag [CharSequence] arr5 (sa/new [String] ["foo"])) 410 | (eval-when-array-class-syntax-is-available 411 | (sa/def arr6 ($ "(sa/new int/2 [[0 1 2 3]])")) 412 | (sa/def arr7 ($ "#double/1 [0.0 1.0]"))) 413 | 414 | (deftest def-test 415 | (is (= (sa/type [int]) (infer arr1))) 416 | (is (= (sa/type [[double]]) (infer arr2))) 417 | (is (= (sa/type [String]) (infer arr3))) 418 | (is (= (sa/type [int]) (infer arr4))) 419 | (is (= (sa/type [CharSequence]) (infer arr5))) 420 | (eval-when-array-class-syntax-is-available 421 | (is (= (sa/type [[int]]) (infer arr6))) 422 | (is (= (sa/type [double]) (infer arr7)))) 423 | (are [form] (thrown? Exception 424 | (binding [*ns* (the-ns 'sweet-array.core-test)] 425 | (eval form))) 426 | '(sa/def arr (identity (sa/new [int] [1 2 3]))) 427 | '(sa/def arr 42) 428 | '(sa/def ^#sweet/tag [String] arr (sa/new [int] [1 2 3])))) 429 | --------------------------------------------------------------------------------