codecombat / codecombat

Game for learning how to code.
http://codecombat.com
MIT License
7.98k stars 4.13k forks source link

Not able to sort by distance with Clojure #2592

Closed Driphter closed 8 years ago

Driphter commented 9 years ago

On the "Zero Sum" level, the following should be equivalent:

(let [coins (.findByType this "coin")
      coin (.findNearest this items)]
  (.move this (.pos coin)))
(let [coins (.findByType this "coin")
      coin (first (sort-by #(.distanceTo this %) items))]
  (.move this (.pos coin)))

The former works correctly, while the latter goes for seemingly random coins.

Vlevo commented 9 years ago

You are right, yet, it seems like both should fail . . . with an error not with "random" coins. You have "items", but shouldn't you have "coins" in both of them.

Clojure is Experimental. (the process from Clojure->JS->Aether, is a little mental (with the most mental being the last bit.))

When I do the second one (making the items->coins change) it goes to the coins in the order generated by .findByType (so it isn't making any change to the original list). iow:

    (let [coins (.findByType this "coin")
            coin (first coins)]            ;; coin (first (sort-by #(.distanceTo this %) coins))]
        (.move this (.pos coin)))

This seems to go into the "nested (too deep) function calls get swallowed by Aether" category.

(def v [2 4 6 8 9 7 5 3 1])

(def x (sort-by - v))
(.say this x)

;; says "9 8 7 6 5 4 3 2 1" as it should

;; but
(def x (sort-by #(dfgre %) v))

;; does NOT error even though "dfgre" is an undefined function. It never even tries to call it.
;; and

(def x (sort-by #(.say this %) v))

;; never says anything...
;; both just blithely return the original input, might as well have coded: (def x v) 
Driphter commented 9 years ago

Thanks for fixing my silly mistakes and also for the investigation you did.

I looked into it further and the map function doesn't work for any user-defined functions either. This is pretty breaking and makes Clojure pretty much useless. It's unfortunate, because I was looking forward to participating in the new tournament.

Also, here's proof (similar to the above):

(def v [2 4 6 8 9 7 5 3 1])
(def x (map #(dfgre %) v))
Vlevo commented 9 years ago

I know this is a different Clojure related question, but I can't figure out how to send a message just to you to ask your opinion

Is my understanding of how clojure should handle "(def )" correct?

(def bar 3)
(defn foo [a] (def bar a))

(foo 6)
(if (> bar 4)     ;; bar should be 6, yes?

I opened issue https://github.com/codecombat/codecombat/issues/2541 about this not working in that fashion and would like the opinion of someone who actually knows Clojure. :)

(I say "global" in my "issue" since I think is only supposed to look for existing defines at higher scope and not create new higher ones. (If that made any sense.))

Thanks! (and I'll delete this if you comment over there to keep this issue clean)

Driphter commented 9 years ago

bar should indeed be 6.

As a side note though, using def in this manner would be considered highly unidiomatic and should be avoided. (See: 1, 2)

(I responded here, because I wasn't sure how to phrase it in the other issue.)

Vlevo commented 9 years ago

I can't see # 2 (no account there)

But as for # 1: I have zero desire to shadow/mask "bar". I don't want a -local/inner- one, I want to use the "outer" one. I don't recall the real piece of code I was trying to write as it was killed by the "Aether nested function" problem anyway. The example above and in the other "issue" are just simplified down to show only the issue at hand.

What would the idiomatic Clojure way be? (or are side effects so disavowed that there is no way.)

Driphter commented 9 years ago

The second link should be fixed now.

It's hard to say what the idiomatic solution would be, since I don't know what the problem is. Clojure gives us many tools to solve problems that could otherwise be solved with mutable globals in other languages. If something like a mutable global was indeed the best solution to the problem, Clojure has ways to make it happen (atoms would be the way to go, I believe).

Vlevo commented 9 years ago

I (who as you have seen doesn't really know what he is doing) have been trying to go through all the codecombat (coco) levels and write new default/example code (and later levels, have the ";;python code" as the default). I think need to go redo them again (Nick hasn't accepted them yet). "real job" was to remove the "(if (do" since that was compounding the "Aether nested function" problem (ANFP), and replace it with either "(if (when" or just "(when" (#2480). But I noticed that we were only teaching "(let" and "(let (let (let" is a ANFP as well and "(let (let" leaves you dangerously close(see below for example). So I was switching to using "(def" not because one shouldn't be using "(let" in a lot of cases, but because I wasn't sure how to teach (in the scope of coco levels) the difference between them and why we (coco) needed to use "def" instead of "let" just because of the ANFP and not because it made better clojure. Which led me to discover that "def" wasn't being scoped properly, by Clojure.js (idiomatic Clojure or not). (pant,pant,wheeze,wheeze).

My path to "(let (let":

(let [fd (.distanceTo this (.findNearest this (.findFriends this)))]
     (.say this fd))
;; and
(let [f (.findNearest this (.findFriends this))
    fd (.distanceTo this f)]
    (.say this ed))

error if no friend ... so

(let [f (.findNearest this (.findFriends this))]
    (if f
        (let [fd (.distanceTo this f)]
            (.say this ed)))

which of course increases the chance we will have ANFP.

Oh, back to the other question (side effect) I just thought of something in python if I wasn't using a global I'd pass back two things: (can you tell I'm a "global" happy script writting Foo" (my professional code writting: ksh,perl,sh,tc/tcl,tcsh (alph-order)) ;)

def foobar ():
    return 3,4
a,b = foobar()

How would one correctly do that in Clojure? "[a b]" as last line?

(defn foobar() [3 5])
(def rvec (foobar))
(def a (first rvec))
(def b (last rvec))

(OK, OK, so I'm a little def happy . . . I need to go find some real clojure code and not just look at the cheatsheet. (preferably, somewhat related type code)

As you may be able to tell, I'm trying really hard to do clojure(ish) within the coco/aether bounds... The main "coco" bound I see would be how does one teach idiomatic for six languages at the same time...

(OK, I've spent to much time on this yeti-esque post, so I hope there aren't too many errors or poor wordings)

Driphter commented 9 years ago

It seems like you've got the general idea of things. I could nitpick style, but that could be seen just as well in the style-guide I linked earlier if you're interested.

Unfortunately, as you've seen, Clojure is at a bit of a disadvantage compared to the other languages. Not everything is supported yet and the issue with nested functions would be crippling to any functional language.

If you'd like to take a look at a bit of CodeCombat Clojure, here's my code from the Criss-Cross tournament a few months back. Some of the code isn't supported by CodeCombat as is since I threw together a little pre-compiler/macro-expander/tree-shaker/compatibility-thingy. As for the default code, I did most of the default code for the older levels, so you could check those out for examples.

If you're really interested in the Clojure way of things, I'd highly recommend checking out Clojure for the Brave and True as well as The Joy of Clojure.

Vlevo commented 9 years ago

yes, lots of index-while-set-blah-blah is such an unsatisfying replacement for a nice "doseq" or your "sort-by"

Your Criss-Cross code is a lot easier on the eyes that I expected after having looked at the style guide. I'm used to the wide open land of w h i t e s p a c e (such as the almost always 4+ spaces kind of rules) instead of two spaces for general indent. The piling the ')'s helps, iow, no: end,end-if,end-while, etc like some langs, be those actual words or hanging closing '}'s .

Criss-cross clojure. Hmm, 4 "(let ", 20 "(defn ", and 32 "(def ", and all the "def"s are against the left edge... Gee, everything is a function (imagine, that). (and none of any length and a_lot/most are only called once). So, would it be a good guess that there is some "rule" like, "if it can be, it should be" (speaking of functions). (I seem to recall having a professor at some point who felt that if a piece of code had more than a couple of lines it probably needed to be split into functions.)

Cool, you wrote the old-clojure code . . . Good thing I hadn't gotten around to butchering those levels. :) I had looked at them, though. (Not many would get butchering even if I wanted to. Just the ANFP related questions of let or def, and if/do -> if/when)

Thanks for the clojure discussion and the links.

Vlevo commented 9 years ago

Yes, atom & swap does seem much better than def, def, def, def, ...

But, Clojure.js doesn't like atoms. :frowning:

(def my-atom (atom 0)) :arrow_right: ReferenceError: atom is not defined

(swap! my-atom inc) :arrowright: ReferenceError: swap$BANG_ is not defined

nwinter commented 8 years ago

Closing for now since we aren't currently supporting Clojure with new interpreter in Aether.