1. guile-ploy is an array library for Guile. Guile's array functions are low-level
  2. and barebones. If you need basic stuff such as a reshape function or a usable
  3. subscript operation, you'll find them here.
  4. guile-ploy doesn't use macros (other than some assert macros that you don't need
  5. to use), doesn't use GOOPS, and it's all Guile Scheme [1]. This is not by design,
  6. but perhaps a sign of immaturity.
  7. [1] guile-ploy requires a version of Guile that provides the functions
  8. array-cell-ref, array-slice, array-cell-set! and array-slice-for-each. These
  9. were added to Guile in 2.2 (2.1.6).
  10. A (slower) replacement for these functions is commented out in ploy/basic.scm,
  11. in case you want to try the library without rebuiliding Guile.
  12. What follows is a description of the library itself. If you aren't interested in
  13. internals you can look up examples of use in test/test-*.scm.
  14. 0. (ploy basic)
  15. (ploy basic) Some definitions to bootstrap the library.
  16. 1. array-map/frame! in (ploy ploy)
  17. This is a version of array-map! that is able to iterate over array slices
  18. (cells), not just over array elements. Now that (array-slice-for-each) is
  19. available, this has become a thin wrapper that handles rank-0 arrays and
  20. return values, since (array-slice-for-each), just as the other Guile array
  21. functions, operates exclusively by side effect.
  22. 2. make-verb in (ploy ploy)
  23. Define a new 'verb', a type of function that applies over arrays and knows the
  24. rank of its arguments. The name is from the J language.
  25. 3. nested-op-frames in (ploy ploy)
  26. array-map/frame! needs the frames of all its arguments to match
  27. exactly. nested-op-frames is a rank extension / rank matching mechanism, modeled
  28. after J. This is so one can do obvious things like
  29. (+verb 1 #(1 2 3 4)) -> #(2 3 4 5)
  30. (+verb #(1 2) #(2 4)) -> #(3 6)
  31. This is similar to 'broadcasting' in other languages/libraries, like
  32. Numpy. There's a good explanation in
  34. nested-op-frames also supports the 'rank conjunction' (w/rank) which can be used
  35. to write the equivalent of nested loops, as explained in that link.
  36. This matching of ranks is done using shared arrays, with no copies.
  37. 4. ply, ply/type in (ploy ploy)
  38. This is an interface to the functionality above that takes also regular Scheme
  39. functions, asigning rank 0 to all of their arguments. So you can do
  40. (ply + 1 #(1 2 3 4)) -> #(2 3 4 5)
  41. The result will have the same array type as the first argument. ply/type allows
  42. you to give an explicit output type.
  43. The verb type includes an oshape field that is used to compute the shape of the
  44. output cell. If this is available, it's used to preallocate the whole
  45. destination array. If the oshape is #f, the result is first computed as an
  46. array-of-arrays and then 'collapsed'.
  47. A difference with J is that the output cells must all have the same shape; no
  48. padding is done. Therefore, it should be possible in principle to compute the
  49. first cell, then use its shape for the whole output array. If the output rank is
  50. 0, collapse is not needed, since the array-of-whatever is already the result we
  51. seek.
  52. 5. (from A . i) and (amend! A val . i) in (ploy ploy)
  53. This is a rectangular selection/assignment operator. The semantics are after
  54. APL. Each element of i is an array of indices into the corresponding axis of A,
  55. and the shape of the result is the concatenation of the shapes of all the
  56. i. This is rather like Octave's or Numpy's subscripting, except that the
  57. arguments can be of any rank.
  58. It is a regular Scheme function, not a verb as defined above.
  59. There are optimizations for 'J-vectors', (like a:b:c in Octave or Numpy slices),
  60. just plain integers, or for taking a full axis at once. These are used to avoid
  61. copying the result when possible. Array indices always cause a copy to be made.
  62. In Numpy or J, negative indices are interpreted as indices from the end of the
  63. axis. This cannot work with general Guile arrays, where the lower bounds can
  64. (unfortunately, since I don't think it's useful) be different from 0. The Octave
  65. feature of using 'end' for the last index of the axis could be replicated for
  66. (from) through the use of macros.
  67. There are examples of use in test-ploy.scm.
  68. 6. ravel, reshape in (ploy ploy)
  69. This is the reshape function of every array language. The semantics are a mix of
  70. J and Octave. It clips or repeats, like J but unlike Octave, but it unravels its
  71. arguments, like Octave but unlike J. Unraveling is in C order.
  72. It does some effort to avoid copying, but I might not have covered every case.
  73. Like (from A . i), these are regular Scheme functions, not verbs.
  74. 7. cat, icat in (ploy cat)
  75. To concatenate arrays over arbitrary axes. Like Octave's cat. (cat) concatenates
  76. over a given axis, while (icat) (= item-cat) concatenates items of the given
  77. rank.
  78. 8. folda, foldb in (ploy reduce)
  79. These are very simple reductions based on J over (/). They operate over the
  80. items of the array:
  81. (folda + 0 #2((1 3 2) (10 20 30))) => #2((11 23 32)) .
  82. folda and foldb are semantically identical. The difference between them is that
  83. folda performs the fold 'above' the frame of its array op, while foldb does it
  84. 'below'. In the example above, the fold is along rows, and the array op is along
  85. columns. In folda, the outer loop is over rows and the inner loop over columns;
  86. in foldb it's the opposite. See also 5.2 of the Repa paper.
  87. They perform very differently depending on the shapes of the arguments, although
  88. a lot of this difference is just an artifact of how crude the implementation is
  89. (e.g. the apply-map-from burden).
  90. I'd prefer the order to be left unspecified, so that fold would decide by itself
  91. whether to fold above, below or even in the middle, if the frame allows. This is
  92. similar to how ply is supposed to work: it resolves to an array-map! call, and
  93. in principle traversal order is decided there.
  94. This functions are a bit of an experiment, I'm not sure they should work as they
  95. do.
  96. 9. as-array in (ploy as-array)
  97. A function to convert array-like objects into arrays. It's a generalization of
  98. Guile's list->array or list->typed-array, similar to Numpy's asarray. Array-like
  99. objects are nested vectors, nested lists, lists of vectors, arrays of lists, and
  100. so on.
  101. This function is needed in two typical cases. One is when interfacing with other
  102. Scheme code that has been written for those array-like types. (Look in
  103. test/test-as-array for ways to do the converse operation.) The other is when
  104. interfacing with other languages/libraries that expect arrays to use a specific
  105. memory layout (e.g. C) and do not accept general strides.
  106. 10. (ploy slices)
  107. This module contains a disorganized list of other array functions: out explode
  108. raze reverse. tile sort. every. any. index are some that I use
  109. often. Unfortunately most of these don't work as verbs but as regular Scheme
  110. functions, since the ply/verb as implemented here mechanism is either too slow,
  111. too inconvenient, or not well-thought enough to handle these functions that
  112. generally apply over full arrays (would have rank -1 in J). This is a sad
  113. situation and I hope to improve on it in the future.
  114. That's it! Thanks for reading.
  115. - lloda