-
Notifications
You must be signed in to change notification settings - Fork 231
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
Clarify behavior of threading macros with fn, short-fn, etc #1478
Conversation
To respond to a comment on the previous issue:
I think it's very desirable! This comment doesn't clarify that, though. There is nothing about I get the impression that you're thinking of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👎🏾
I don't know if Clojure was the originator of Of note might be this bit: ;; Be cautious with anonymous functions; they must be wrapped in an outer
;; pair of parens.
(-> 10
#(/ % 2))
;; will throw an exception, but
(-> 10
(#(/ % 2)))
;; will work fine. Similarly,
(-> 10
(fn [n] (/ n 2)))
;; will throw an exception, but
(-> 10
((fn [n] (/ n 2))))
;; works as intended. Janet's So in Janet we'd have:
Note that Clojure's docstring for
...and the "unexpected" behavior got documented by users elsewhere. FWIW, there is a somewhat similar site for Janet here with a page for There have been remarks made in the past along the lines of "memory isn't free", so although I can related to wanting a place to look for gotchas and the like, I'm not sure the docstrings are the best place for them. [1] In Janet you can also do stuff like [2] I went ahead and added a couple of examples, but others are ofc free to do similarly :) |
@ianthehenry, I'm not saying that I think in my case, the root of the issue is my familiarity with other languages' "pipeline operator", which behaves as I expected (-> value
some-func
another-func
yet-another-func) then you would naturally intuit that this was a function that takes functions, because that's exactly what it looks like. Even if you look up the docs and read about it, first impressions are a powerful thing. On the other hand, if it looks like this: (-> value
(some-func)
(another-func)
(yet-another-func)) then, assuming you know those functions' signatures, your first impression is that this code is outright wrong. You would then wonder why this code ever worked, look up Programming language design is a kind of UI design. Yes, programming languages are specialist tools and should assume that their users are specialists, but that's no reason to make things that are, frankly, outright misleading for the sake of letting people who are already familiar with them shave off a few characters.
If a few hundred bytes of memory are really that significant an issue, @sogaiu, then there should be an option to simply discard docstrings as code is loaded. That would save comparatively enormous amounts of memory, without forcing the docs to skimp on quality. Python does something similar, with its "optimizer" that (IIRC) just deletes docstrings from compiled bytecode and does nothing else. |
Regarding discarding / unbundling of docstrings, please see this issue. I don't think it's a bad idea but (depending on what means precisely) it doesn't exist yet [1]. In case it isn't apparent, AFAIU, one of the use cases for Janet is for it to be embedded and so in that context size can make a difference. Including certain types of remarks in docstrings (vs excluding) can over time and space add up (there are a lot of built-in callables in Janet). In the mean time, I think what was suggested above about placing certain types of clarifications elsewhere makes sense, though now, some of the concerns have been touched on at janetdocs. I'm sure additions would be welcome if what got added was insufficient. [1] IIUC, janet can be built without docstrings using this. The resulting janet is considered non-standard though -- presumably it was considered better to have some sorts of docstrings in many cases so removing them outright is considered an extreme measure. |
@na-sa-do I perceive that you are comfortable with direct communication. Permit me to engage in some. It seems to me that you are struggling with the concept and behavior of the You object above to the way that The fact of the matter is, a macro is not a function and the semantics of the two are subtly different. To wit: macros do not operate upon the compiled state of their arguments, but upon the literal tokens that they are passed. In other words, at the moment when a macro executes there is no such thing as a "function," per se. At macro-execution time, there are only such things as values and data structures containing values.[0] The entire purpose of macros is to enable rearrangement of tokens prior to compilation of those tokens. This allows macros to receive invalid Janet syntax and produce valid Janet syntax by following the macro's internal logic—every macro, from It is the established convention for
At macro execution time a form like It is only after macro execution time, when the form has been re-written and then inserted back in place, that any evaluation or function application happens—the final threaded form is what is handed to the compiler, at which point function calls can start to happen as usual. You accuse Structs and tables are not lists. That means that the Janet 1.35.2-872b39cc linux/aarch64/clang - '(doc)' for help
repl:1:> (-> :a {:a 1 :b 2})
1
repl:2:> (-> :a {:a :z :b :y} {:y 1 :z 2})
2 Keywords similarly are not lists. That means that the Janet 1.35.2-872b39cc linux/aarch64/clang - '(doc)' for help
repl:1:> (def Obj @{:value 1
repl:2:({> :triple (fn [self]
repl:3:({(> (* 3 (self :value)))})
@{:triple <function 0x006EE68B1F90> :value 1}
repl:4:> (-> Obj :triple)
3 Add to this the known case of symbols that you've brought up. Symbols are not lists. That means that the Once again: you have accused Supposing that the programmer has an accurate mental model of what macros are and how they operate, this simply is not strange, ambiguous, or confusing. What else could a threading macro do with a symbol or a keyword or a dictionary, if not make it capable of being threaded into by wrapping it in parentheses? And what else could a threading macro do with a form like So then, finally, to your argument that treating bare symbols this way creates a footgun for new programmers who are just being introduced to the language. Your opinion seems to be that this makes learning the language more difficult than necessary. On the contrary, I say: the Do [0] Technically I'm lying a tiny bit here—functions do exist at macro execution time, but they exist as values and have no special behavior any different from any other value—say, a number, a symbol, or a keyword. |
I will grant that I was mistaken, but not about that. I'm already familiar with macros in other languages that have them, most notably Rust. However there's a critical difference between Rust macros and Janet macros (or Lisp-like languages more generally), which I'd not realized until just now. Consider this Janet code. (foo bar) Can you tell me whether foo(bar); // function
foo!(bar); // macro The exclamation mark serves as an unambiguous signal that you are exiting the realm of normal Rust and entering a DSL of some description. Here be dragons, etc. In the Rust community, convention is to always include the
Once again, I will grant that I was mistaken, but it doesn't change my argument. Consider a Janet in which... hang on, what's the function to append to arrays... Hold on a second. I was about to use a hypothetical example in which the function for concatenating arrays and the function for adding one element to the end of an array were merged together, and it guessed which sense you meant based on whether it was passed an array or not, thus forcing every call site to explicitly guard against whichever behavior they didn't mean or else become ambiguous between two operations that are obviously distinct and should not be conflated. But, um, janet:1:> (array/concat @[] 1)
@[1]
janet:2:> (array/concat @[] [1])
@[1] You live like this?
Not do that, and instead fail with a useful error message.
Notice that (Okay, in the very specific case where the |
Closing this as the change is overly pedantic and possibly confusing. Using |
See #1477.