-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implementation of RandomAccess class #1007
Conversation
EDIT: Looks like my concerns might be addressed using the coalton:specialize facility. The following can augment the example code u/stylewarning wrote to resolve the issue I raised below. (coalton-toplevel
(declare double!-sf (SingleFloats -> Unit))
(define (double!-sf a)
(let ((len (size a)))
(lisp Unit (a len)
(cffi:with-pointer-to-vector-data (ptr a)
(cl:print a)
(cffi:foreign-funcall "cblas_sscal" :int len
:float 2.0
:pointer ptr
:int 1))
Unit)))
(specialize double! double!-sf (SingleFloats -> Unit))) Pre-EDIT: Let me know if I'm missing something. With this design, you either have a function defined for a particular specialized array type, or the entire However, say, with a CUDA array, the example implementation of A related issue is about being able to specialize the implementation of, say, (coalton-toplevel
(define-class (ArrayDoubler :f :t (:f -> :t))
(double! (:f -> Unit))))
(coalton-toplevel
(define-instance (ArrayDoubler SingleFloats Single-Float)
(define (double! a)
(let ((len (size a)))
(lisp Unit (a len)
(cffi:with-pointer-to-vector-data (ptr a)
(cl:print a)
(cffi:foreign-funcall "cblas_sscal" :int len
:float 2.0
:pointer ptr
:int 1))
Unit))))) |
library/array.lisp
Outdated
(cl:setf (cl:aref a n) x) | ||
Unit))))) | ||
|
||
(%define-unboxed-array cl:single-float Single-Float SingleFloats) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a really useful type, and I could see myself reworking a lot of stuff with this in mind.
I wonder if there might be utility in having a macro/function like define-type-with-array
that would automatically add an array type when you define the new type. I know that's probably a later stage polish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't unboxed-arrays be limited by the underlying lisp implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Izaakwltn Only some kinds of base types might benefit from an array type in Lisp. Other types might need more finesse, like, for example, an unboxed array of RGB values, which might require working with foreign memory.
Moreover, it's possible that we define (Vector :t)
as a kind of storage of :t
, so that there's at least one generically useful parametric storage type for all types :t
, even if it's not as optimized as DoubleFloats
and friends.
@digikar99 Another possibility is to make a new type for C data, here's a sketch:
This doesn't contain an implementation that lets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Should add instances of (at least) the following:
- IntoIterator
- Semigroup
- Monoid
- Eq
- Foldable
This might prove uncomfortable.
Because the instance defintion
(define-instance (Array :f :t => IntoIterator :f :t) ...)
does not refer to any actual types in the array package, you need to define instance in the coalton-library/iterator
package itself.
The same would be true for Eq
the other "fundamental" typeclasses. This may indicatie that Arrray
should be thought of as a fundamental class and be redefined in coalton-library/classes
.
I don't think this is unreasonable, Array
is indeed defining something fairly abstract and fundamental - a strict relationship between two types, one of them representing a reified mutable map from bounded naturals to the other type; a fairly fundamental concept for computing. Not quite as abstract as Functor
, for example, but at least as abstract as Num
and fromInt
in my opinion.
Does anyone have a preference for names? To me, |
I'm not particularly wedded to any particular convention. I didn't like that (in my initial experiment) things like As a general comment, I myself am a big fan of idioms and colloquialisms if there's good reason to introduce them. Sort of like how we chose the name
But as I said, I'm not so opinionated that I'd veto a majority opinion. |
That's a good point. But, can some hyphens be thrown in, eg |
I think having a parametric array type is the correct approach. The compiler support required would be minimal, just extending This design requires users to define new types (by calling an internal macro?) for each type they want to store in an array. A single parametric type would avoid this entirely. |
A parametric type may (eventually) have specialized representations, but I argue an array class is higher priority, if only to allow multiple array-like objects to be accessed uniformly. We do this in other Lisp code, where we work with Lisp and foreign arrays. (Haskell has I'm actually slightly against a
The second option is what I find both of these choices suboptimal: Arrays (in the context of this PR) are supposed to be a ticket to efficient code with well known space-time properties. Option #1 violates expectations of parametric polymorphism (i.e., There's a third issue. Should general array processing code seek parametric or ad hoc polymorphism? I argue ad hoc for essentially these reasons:
While a |
(As a secondary comment, I am interested to hear more about the minimal changes needed to support such a feature, since we'd need to do it for complex numbers, probably.) |
One downside of my approach of having a handful of separately named types is that it's inherently non-portable. Some Lisp implementations, for instance, will specialize |
After some discussion with @macrologist I've renamed
Some alternate names that were considered but eliminated as possibilities: In addition to the renaming of the class to In general, though, I fully expect "efficient code" (TM) to use I guess the question of whether there should be some concrete collection types in this PR blessed by the standard library is still open. |
Further side note: This PR continues to add to the confusion of |
I think what I will do to move on this PR is remove the specific type definitions and keep the class + Vector, and we can move in a separate PR what to do about type, esp. since I started working on the type specialization stuff in a separate PR. |
I'm in favor of separating out the prs for generic random access and specialized arrays. Also is there a reason not to track readonly/writable at the type level? |
I didn't think of a good way to approach it. In my mind I thought about examples like mmaping a file, which itself might possibly be R or W only, depending on the UNIX permissions. With this, I imagined a hypothetical With that said, I didn't think about it too deeply beyond that. Do you have any design thoughts on/sketched of this? |
f4c3ead
to
2f7253b
Compare
@eliaslfox I stripped out everything but the class (and a couple instances). |
This adds a new class to the standard library (RandomAccess :F :T) allows the storage of elements of type :T inside of a storage type :F with efficient O(1) read/write access. The class implements a few instances (which could be expanded to all reasonable efficient types), and uses the convention of adding an 's' to the base type (e.g., a storage of Double-Float is DoubleFloats). We don't keep "legacy" '-' in existing names. All operations become efficient Common Lisp code, up to inlining.
EDIT: See discussion below on the renaming from
Array
toRandomAccess
.EDIT: This PR now just implements the class and a couple obvious instances.
This adds a new class to the standard library
(Array :F :T)
allows the storage of elements of type:T
inside of a storage type:F
. The design requires:F
be associated with only one type:T
, so there's no possibility of doing any type shenanigans, such as accessing aU8
from aU64
array, even if that's valid in principle. As explained in a comment, this trades conceptual flexibility for much higher ergonomics.The class implements a few instances (which could be expanded to all reasonable efficient types), and uses the convention of adding an 's' to the base type (e.g., a storage of
Double-Float
isDoubleFloats
). We don't keep "legacy" '-' in existing names.[Side note: I decided against having a general
(Array :t)
type, which I think would require a bunch of built-in compiler support. While it may still be a good idea worth discussing, it seems like it's not needed, and leads to a less flexible design anyway. (What if we want an array allocated in C-space? Or a GPU buffer?) Moreover, we have other data structures for expressing generic sequence data... having parametric polymorphism at the type level means, at some point, a fabled(Array :t)
would have to just be a list of pointers anyway, as it is in Common Lisp.]All operations become efficient Common Lisp code, up to inlining. There is some example code which includes using the monomorphizer.
This is a proposed base design, and I welcome feedback on the design, and any additional requests for implementation.
I suggest to myself the following before merging:
CC @digikar99.