Systematize and Complete the Coalton Collections Library #1329
Replies: 5 comments 10 replies
-
|
Also, I think that some of the list functions could be more efficient if they used mutation internally, while still not "leaking" any mutability. For example, (I'm not going to fix this here, but I'll probably raise it as a separate issue.) |
Beta Was this translation helpful? Give feedback.
-
|
After more work, I think the originally proposed method of collection inheritance is going to introduce more complexity than I'd initially anticipated. Particularly, it will make working with LinearCollections less intuitive. The definitions: (define-class (Collection :m :a (:m -> :a))
...
(define-class (Collection :m (Tuple :k :v) => KeyedCollection :m :k :v (:m -> :k :v)))
...
(define-class (KeyedColleciton :m UFix :a => LinearCollection :m :a)
...run into some issues with wanting the Collection methods to be described inconsistently for KeyedCollections and LinearCollections. For example, take (filter (fn (key-value) (> (snd key-value) (fst key-value))) my-map)However, suppose you wanted to do something simple like remove all of the negative integers in a list of integres. You'd have to do something like this, because all LinearCollections are KeyedCollections with UFIx keys, which are in turn Collections of (UFix :a) tuples. (filter (fn (index-value) (>= (fst index-value) 0))) my-list)That's obviously untenable as a solution for the library. The obvious answer, if you want to maintain the class hierarchy, is to add a (filter-values (fn (index-value) (>= (fst index-value) 0))) my-list)And that's not so bad. The
For KeyedCollections, a more specialized and less commonly used datatype, all of these functions should exist. They are a more flexible datatype and they'll incur extra complexity for it. But, LinearCollections are the most used and should probably be the simplest of the types of Collections. One solution is to break the hierarchy up so that KeyedCollection and LinearCollection are both Collection instances, but LinearCollections are not KeyedCollection instances. A second solution is to break it up so that a LinearCollection is a KeyedCollection and a Collection, but a KeyedCollection and a Collection have no relationship. Either way, there are going to need to be some redundant functions defined. I'm currently leaning towards the second solution, because I imagine it would be useful in more scenarios to perform operations like splitting an ordered map than it would be to treat a map like a generic collection of tuples. |
Beta Was this translation helpful? Give feedback.
-
|
If there's anything I can do to make the FSet collections more useful to you, please file an issue. |
Beta Was this translation helpful? Give feedback.
-
|
I'm a fan of the organization that you're proposing and I like some of the conventions you introduced in your PR, like I had a different view on how type classes would be mixed into this that I wanted to suggest. I like the idea of sorting our collections into files and directories with consistent naming conventions. However, I don't think that we should use type classes to describe the inheritance, for example, from ;; basics:
;; could be implemented by List, HashSet, HashMap, OrdTree, OrdMap, etc.
(define-class (Eq :elt => Set :col :elt (:col -> :elt))
(empty :col)
(union (:col -> :col -> :col))
(intersection (:col -> :col -> :col))
(difference (:col -> :col -> :col))
(element-of? (:col -> :elt -> Boolean)))
;; could be implemented be (Cell (List :t)), Vector, etc.
(define-class (Stack :col :elt (:col -> :elt))
(empty? (:col -> Boolean))
(push! (:elt -> :col -> Unit))
(peak (:col -> Optional :elt))
(pop! (:col -> Optional :elt)))
;; slightly more experimental:
(define-class (Iterable :col :elt (:col -> :elt))
(%docollection (:col -> (:elt -> Unit) -> Unit))
(%somecollection (:col -> (:elt -> Boolean) -> Boolean))
(%everycollection (:col -> (:elt -> Boolean) -> Boolean))
(%bestcollection (:col -> (:elt -> :elt -> Boolean) -> :elt)))
(declare contains? ((Eq :elt) (Iterable :col :elt) => :col -> :elt -> Boolean))
(define (contains? col elt)
(somecollection (e col)
(== e elt)))There are just some features that are unique to certain data structures. For example, What do you think? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
The current Coalton collections library suffers from several problems.
Here is a propsoal to standardize and complete the collections library, fixing the above problems:
COALTON-LIBRARY/COLLECTIONS/IMMUTABLE/andCOALTON-LIBRARY/COLLECTIONS/MUTABLE/packages. This mirrors the Scala organization, and it's very nice! Move packages likeCOALTON-LIBRARY/SLICEwhich support the collections library to the rootCOALTON-LIBRARY/COLLECTIONSpackage.remove-whereremoves the first element that matches a predicate.remove-atremoves the element at an index.find-wherereturns the first element matching a predicate.index-wherereturns the index of the first element matching a predicate.contains-where?returns a bool if you're matching a predicate, etc. Unfortunately this means discarding some of the historical Lisp functions likecar, but those can remain specific to the List type in case anyone likes to use them!Ord-Map:insert :: ∀ :A :B. ORD :A ⇒ ((MAP :A :B) → :A → :B → (OPTIONAL (MAP :A :B)))should be avoided, because the Optional return type only provides information, not safety, to the user at the expense of ergonomics. Another example, signatures likeList:take :: ∀ :A. (UFIX → (LIST :A) → (LIST :A))should be favored overList:tail :: ∀ :A. ((LIST :A) → (OPTIONAL (LIST :A))). In both cases a reasonable return value can be constructed, and tail sacrifices ergonomics but gains neither safety nor information. Individual collections can have concrete functions that take this approach to support particular use-cases, but they shouldn't be on the generic interface.find-where odd? xs, etc. Second, it's more useful with currying because that way you can thread collections through partially applied collection operations without needing to construct intermediate lambdas.Here are a few elements of the proposal I'm not sure about, and should definitely be discussed:
get, etc, liberally. The current paradigm is to use the conventionfunction-unsafefor those functions. I suggested use of the#symbol (it's sharp, so it's pretty unsafe - it could poke an eye out!) instead for better ergonomics.get#instead ofget-unsafe,insert!#instead ofinsert-unsafe!, etc.Setas an out-of-the-box default for the immutable set. But, you can importscala.collection.mutable.Setand nowSetcreates a mutable set, and the compiler doesn't complain like Common Lisp's does.Finally, this is a big, breaking change - even for < 1.0 software like Coalton. It can be done in pieces:
Here is the proposed TypeClass hierarchy:
Collection :a:aelements.Monoid,Applicative,Functor,Monad,TraversableImmutableCollection :aCollection :aSet*MutableCollection :aCollection :aMutSet*KeyedCollection :k :vCollection (Tuple :k :v)ImmutableKeyedCollection :k :vKeyedCollection :k :vImmutableCollection (Tuple :k :v)Ord-MapMutableKeyedCollection :k :vKeyedCollection :k :vMutableCollection (Tuple :k :v)HashmapLinearCollection :aKeyedCollection (Tuple UFix :a)ImmutableLinearCollection :aImmutableKeyedCollection (Tuple UFix :a)LinearCollection :aSeq,ListMutableLinearCollection :aMutableKeyedCollection (Tuple UFix :a)LinearCollection :aVector,Queue,Lisparray(* - Set does not currently exsit)
Here is a chart showing the typeclass hierarchy along with the existing collections (plus mutable and immutable Sets):

Finally, because it's too big to include here, I've created a spreadsheet with the proposed functions and the status of the current collections. I'll continue to clean this up and add which typeclass each function should belong to.
Beta Was this translation helpful? Give feedback.
All reactions