jaunt-lang / jaunt

[ABANDONED] A jaunt away from Clojure
https://github.com/jaunt-lang/jaunt/issues/157
134 stars 6 forks source link

Should sets be contains? predicates rather than identity mappings? #151

Open arrdem opened 7 years ago

arrdem commented 7 years ago

To be clear, I'm no fan of datastructures as functions, but this is a thing that Clojure does and Jaunt does for now. The particular case I'm interested in is the common and convenient idiom of using a set #{} as a predicate since \forall e \in s, (s e) \to e.

The problem is these bastards....

user=> (#{false nil 1} false) ; insane/unexpected non-truthy result
false
user=> (#{false nil 1} nil) ; insane/unexpected non-truthy result
nil
user=> (#{false nil 1} 1) ; expected truthy result
1

This suggest that for most cases, the following behavior would be more obvious:

user=> (#{false nil 1} false)
true
user=> (#{false nil 1} nil)
true
user=> (#{false nil 1} 1)
true

That is sets operate as contains? predicates. I'm sure this breaks some class of expressions where you bind the result of a set predicate and when it's not nil do something with it, but do I really care? This is a pretty simplifying change which removes an obvious pitfall.

simon-brooke commented 7 years ago

OK, I appreciate you're the person doing all the work...

There are many design decisions in Clojure which I personally would not have made. The above is one of them. However, one of the benefits of working with the Clojure semantics is that there is a huge body of libraries for Clojure, much of which are pure Clojure (i.e. don't have dependencies on Java or other host platform). If Jaunt sticks to Clojure semantics, then all this library code will Just Work.

So yes, I agree that this is ugly and that your suggestion is more elegant, but I'd nevertheless recommend not introducing potentially-breaking changes. Something, somewhere, relies on these semantics, and changing them could introduce very hard-to-find bugs.

arrdem commented 7 years ago

Hey Simon, thanks for the comment.

As the Administration section of the README states, my goal with Jaunt is not to produce or maintain a Clojure compatible language. Because there is no specification for Clojure now or in the foreseeable future, any change(s) I make in Jaunt beyond merging patches from Clojure are at a formal semantics level breaking. In fact, Jaunt already contains several patches which are breaking changes with regards to Clojure. I refactored some internal details of clojure.lang.Namespace which boot depends on and as a result boot won't run Jaunt. The goal of Jaunt for me is to experiment and question.

No value has been lost here. Things that work in Clojure still work fine in Clojure. I've just failed to add value to the Clojure ecosystem.

Were I a paid maintainer with a product, or attempting to contribute to Clojure itself that sort of incrementalism would have its place. Declaring compatibility and falling into the mindset of "value lost by breaking things" is exactly what demotivated me from working on this project for the last several months. I refuse to load myself as a single maintainer down with the intractable goal of maintaining behavioral parity while simultaneously trying to improve things.

That said, I'm open to arguments that the above existing behavior is useful. This particular sharp edge in the standard library isn't something I've been bitten by personally, it came to my attention in this issue - https://github.com/ptaoussanis/truss/issues/4 and I thought that taking this possibility under consideration fit well into the general goals of this project which are essentially to question everything about Clojure's internal architecture and user experience.

mikera commented 7 years ago

I actually like this change. Language features are generally good if they ensure logical invariants, e.g.

;; for any set s, value a
((conj s a) a) => true
((disj s a) a) => false

Among other things, these kind of logical invariants: