rongarret / ergolib

A library designed to make programming in Common Lisp easier
140 stars 10 forks source link

Add set operations #4

Open sabu36 opened 3 years ago

sabu36 commented 3 years ago

Suggested in #3

I took the liberty of renaming sforce (for string) into strforce even though you didn't affirm it. By the way, it's interesting that in binding-block you indent body on par with parameters unlike in let.

EDIT: I think the reason why I slightly prefer s- for sets and str- for string may be that a set of things seems more natural than a string of things, making set feel more fundamental than string.

rongarret commented 3 years ago

The problem with this whole approach is that it doesn’t scale well. There are potentially a lot of data structures that one might want to force iterators to, and coming up with a new name for every possible target data type doesn’t scale.

This is a hard design problem that I’ve been wrestling with for a long time, because it’s not unique to FORCE. There are a lot of functions where there could be lots of reasonable options for the returned data type, and it would be nice to have a more general way of extending a function like that to specify the one you want.

I think the Right Way to do this for FORCE is to add an optional argument that specifies the target type, so instead of SFORCE or STRFORCE you would call (FORCE thing ‘string) or (FORCE thing ’set) or whatever. Analogous to COERCE. That works for FORCE but it does not work in general. But that might be good enough for now.

rg

On Dec 23, 2020, at 5:57 AM, sabu36 notifications@github.com wrote:

Suggested in #3

I took the liberty of renaming sforce (for string) into strforce even though you didn't affirm it. By the way, it's interesting that in binding-block you indent body on par with parameters unlike in let.

You can view, comment on, or merge this pull request online at:

https://github.com/rongarret/ergolib/pull/4

Commit Summary

Allow making a set from an iterable Add scollect, which collects into a set Overload filter for a set Add difference operation for sets Rename sforce into strforce; Add sforce, collecting into a set File Changes

M core/ergodict.lisp (14) M core/ergoutils.lisp (6) M core/iterators.lisp (17) Patch Links:

https://github.com/rongarret/ergolib/pull/4.patch https://github.com/rongarret/ergolib/pull/4.diff — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

sabu36 commented 3 years ago

In Racket, there are for/list, for/vector, for/or, for/fold, etc. So I guess another way is: force/set and collect/set etc. Not sure which way is better.

sabu36 commented 3 years ago

Another idea—using analogy from casting:

> (defc basket (make-set :items '(1 2 3 4 5)))
BASKET
> (for x in basket -> (* 2 x))
#<Set (10 8 6 4 2)>
> (for x in basket ->vector (* 2 x))
#(2 4 6 8 10)
> (for x in basket ->list (* 2 x))
(2 4 6 8 10)

By the way, I'd not noticed yesterday but today (require :ergolib) produced an error:

*** - The macro COLLECT may not be called with 1 arguments: (COLLECT ITEM)
The following restarts are available:
SKIP           :R1      skip (DEFUN SFORCE # ...)
…

When I commented out sforce and (require :ergolib) it worked but when I then uncommented it also worked without error. Thinking it had something to do with require order, I moved sforce to ergodict and it was fixed.

sabu36 commented 3 years ago

Hey, I've managed to implement the last idea—FOR with an arrow—it accentuates change of type when it happens:

CL-USER> (defc mylist (list 1 2 3 4 5))
MYLIST
CL-USER> (defc myvector (apply #'vector mylist))
MYVECTOR
CL-USER> (defc mystring "abcde")
MYSTRING
CL-USER> (defc myset (make-set :items mylist))
MYSET
CL-USER> (forw x in mylist ->vector (* x x))
#(1 4 9 16 25)
CL-USER> (forw x in myvector -> (* x x))
#(1 4 9 16 25)
CL-USER> (forw x in mystring ->list (char-upcase x))
(#\A #\B #\C #\D #\E)
CL-USER> (forw x in myset ->string (* x x))
"1625914"
CL-USER> (forw x in myset -> (* x x))
Collector for SET is not defined
   [Condition of type SIMPLE-ERROR]
CL-USER> (create-type-collector 'set #'make-set #'add)
#:DUMMY13068
CL-USER> (forw x in myset -> (* x x))
#<Set (25 9 4 16 1)>

One puzzling and alarming thing is that creating a filter function with it leads to error although as a macro it works:

CL-USER> (defun autofilter (thing function)
           (forw item in thing if (funcall function item) -> item))
;Compiler warnings :
;   In AUTOFILTER: Unused lexical variable FUNCTION
AUTOFILTER
CL-USER> (autofilter myset #'oddp)
;Compiler warnings :
;   In #:LOOP13144 inside an anonymous lambda form: Undeclared free variable FUNCTION
;   In an anonymous lambda form: Undeclared free variable THING
Unbound variable: THING
   [Condition of type UNBOUND-VARIABLE]
CL-USER> (defmacro autofilter* (thing function)
           `(forw item in ,thing if (funcall ,function item) -> item))
AUTOFILTER*
CL-USER> (autofilter* myset #'oddp)
#<Set (5 3 1)>

All the existing filters are defined as functions. I don't exactly understand why it doesn't work but I wonder if you know a way around this.

EDIT: By the way, I finally realized that, in BB, I can crisscross definitions and statements instead of having to put all definitions at the front—that explains the indentation.

sabu36 commented 3 years ago

In fact, I think this order makes more sense:

(over <thing> as <var-name> -> <expression>)

When the expression for "thing" is long, I don't have to remember the variable name, which becomes relevant only later in the final expression.