Skip to content
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

Pyrsistent #33

Open
gilch opened this issue Feb 9, 2017 · 8 comments
Open

Pyrsistent #33

gilch opened this issue Feb 9, 2017 · 8 comments
Labels
feature New feature or request

Comments

@gilch
Copy link
Member

gilch commented Feb 9, 2017

One of the nice things about Clojure is the persistent immutable data structures. These have been implemented in Python via the Pyrsistent library (and perhaps others).

It might be nice if we could use some of these with a short reader macro. It seems like a common desire for those coming from Clojure. Rather than letting everyone implement their own macro versions, this might be worth putting in contrib.

A more radical step would be to make Hy's default data structures use the Pyrsistent versions, and make the Python native structures available via the reader macros.

I'm less comfortable with that idea. It would create a dependency and might be less compatible with Python libraries or potentially future Python syntax. Though since Python is duck typed, probably not that much less compatible. Also, Pyrsistent is MIT licensed, so it wouldn't be that much of a problem as a dependency.

@refi64
Copy link

refi64 commented Feb 9, 2017

+1 to the idea of using reader macros, -1 to changing Hy's defaults. Part of the awesomeness of Hy comes from the fact that it's ultimately still Python, and changing that would partly lose that benefit, along with slowing things down quite a bit.

@Kodiologist
Copy link
Member

Yeah, adding some syntactic sugar for these things to hy.contrib seems fair enough, but making [1 2 3] create something other than a plain list by default is asking for trouble.

@gilch
Copy link
Member Author

gilch commented Feb 23, 2017

Something as simple as

(import pyrsistent)

(defreader p [form]
  `(pyrsistent.freeze ~form) )

would get us pretty far. That's enough for PVector PMap and PSet. The freeze function is recursive, so it also works on nested structures,

=> #p[#{1 2} {1 "a"  2 "b"}]
pyrsistent.freeze([{1, 2}, {1: 'a', 2: 'b', }])
pvector([pset([1, 2]), pmap({1: 'a', 2: 'b'})])

but there are limitations:

=> #p #{[1 2] {1 "a"  2 "b"}}
pyrsistent.freeze({[1, 2], {1: 'a', 2: 'b', }})
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: unhashable type: 'dict'
=> #p #{#p[1 2] #p{1 "a"  2 "b"}}
pyrsistent.freeze({pyrsistent.freeze([1, 2]), pyrsistent.freeze({1: 'a', 2: 'b', })})
pset([pmap({1: 'a', 2: 'b'}), pvector([1, 2])])

This may or may not be what we want.

There's a lot more to Pyrsistent than freeze though.

@Kodiologist
Copy link
Member

Kodiologist commented Feb 27, 2017

Although, PVector and PSet seem equivalent to Python's built-in tuple and frozenset, respectively.

@gilch
Copy link
Member Author

gilch commented Mar 12, 2017

The interface seems basically the same, but I think PVector/PSet share data between revisions in a trie like Clojure does. I'm pretty sure Python's tuple and frozenset implementations don't do that.

@Kodiologist Kodiologist added the feature New feature or request label Apr 26, 2017
@gilch
Copy link
Member Author

gilch commented May 20, 2018

Perhaps we should put this in extra with an optional dependency for Pyrsistent. This would keep Hy's core install smaller.

@Kodiologist Kodiologist transferred this issue from hylang/hy Mar 5, 2022
@gabriel-francischini
Copy link
Contributor

gabriel-francischini commented Jun 4, 2022

I think that frozen and thaw are the ones that would benefit the most from a reader macro.

I'm not a fan of the #p syntax, but it gets the job done.
In common lisp, it's a convention to use *earmuffs* to name a variable that has special properties (* for nonlexical/dynamic variables and + for constants). Here's a mock example:

;; Players can join in from the network, so this variable is a global that has a different
;; mutability behavior (e.g. it may not be completely safe to use it within MAP or FILTER lambdas
;; because it may change between calls, so beware of its different mutability behaviors).
;; Check [some documentation link/some functions] to see how to read and write to it safely.
(defvar *current-players* 100) 

;; For some reason, we have a hard limit of players in a single game session.
(defconstant +maximum-players-allowed+ 32768)
;; Somewhere far away from the previous declarations:
(defun create-server-with-exact-number-of-players (target-number
                                                   players-to-migrate-to-a-new-server)
  "This function groups players, one at a time, into a new server with `target-number` players.
   Useful for raid bosses in the final level of a dungeon."
  (setq players-added-to-new-server 0)
  (map (lambda (player)
         (when (< *current-players* target-number)  ; beware! *current-players* is a special variable!
           (inc players-added-to-new-server)

           ;; This decrement may not work properly, and the *earmuffs* are a clear indicator
           ;; that we should double-check that we are doing what we intended.
           ;; A quick glance at *current-players*'s documentation should remind us of
           ;; what is the correct thing to do in this case.
           (dec *current-players*)))
       players-to-migrate-to-a-new-server)
  (return players-added-to-new-server))

Maybe we could do something similar?

;; freezes
#+[1 2 3]
#+ #{5 7 11}
#+ MyVar

;; thaws
#- MyFrozenVector
#- MyFrozenNestedDictionary

I don't know how the community feels about earmuffs, because in an earmuff-heavy codebase this could happen:

(setv +my-vector+ #+[1 2 3])
(setv +my-set+ #++my-vector+)

But I'm not sure that #+MyVar or #-MyFrozenVector are even legal, so #++my-vector+ probably is a non-issue.

@Kodiologist
Copy link
Member

But I'm not sure that #+MyVar or #-MyFrozenVector are even legal

They aren't, or rather, they'll try to call the wrong reader macros. You need a space to terminate the reader macro name, as in #+ MyVar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants