Is it possible to define mutually recursive functions in Janet? #1254
-
I was trying to write something, and got an error regarding trying to call an undefined function in a function definition. "Silly me," I thought, "I'll just..." What? For example, consider the following (defn f [n]
(if (zero? n)
"f-done"
(g (dec n))))
(defn g [n]
(if (zero? n)
"g-done"
(f (dec n)))) Though I didn't expect it to work (and indeed it doesn't) I tried first defining Another thought was to just define (def g nil)
(defn f [n]
(if (zero? n)
"f-done"
(g (dec n))))
(defn g [n]
(if (zero? n)
"g-done"
(f (dec n))))
(defn f [n]
(if (zero? n)
"f-done"
(g (dec n)))) This has a similar issue in that for sufficiently large numbers (such as Two workarounds I can think of are to
The last one isn't ideal as the whole point for me is to have small, clean functions. Passing a function as part of the argument seems like it should work, but it just feels kind of icky to me for some reason. edit: Considering this is something even Python can do, perhaps I should have assumed that I'm missing some key piece of information, but at the time of writing this, it seemed like Janet's "everything is a closure" nature prohibits mutually recursive function definitions. edit2: Indeed, me and my immutable brain overlooked something quite simple. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 19 replies
-
I would normally use (var g nil)
(defn f [n]
(if (zero? n)
"f-done"
(g (dec n))))
(set g
(fn [n]
(if (zero? n)
"g-done"
(f (dec n)))))
(print (f 10)) You can make it cleaner by (var g nil)
(defn f [n]
(if (zero? n)
"f-done"
(g (dec n))))
(defn- g* [n]
(if (zero? n)
"g-done"
(f (dec n))))
(set g g*)
(print (f 10)) |
Beta Was this translation helpful? Give feedback.
-
I’ll just mention that Janet has varfn for this purpose:
https://janetdocs.com/varfn
…
On Aug 14, 2023 at 1:34 PM, <Chloe Kudryavtsev ***@***.***)> wrote:
Many professional programmers often never end up grasping the difference because understanding stuff like that is a fundamentally different skillset.
My education is actually in philosophy and english, which is an unfair advantage in that regard (on top of working in tech) :)
To answer the question concisely: these aren't tricks at all, and just things programmers should simply know, as they are merely consequences of how things are/work. In other words, the "tricks" are descriptive, rather than active.
For this specific one, here's how I recommend thinking of it:
Immutable/static/constant/etc all describe something not changing.
What that something is varies, and has different behaviors.
A binding is saying "this symbol (a type in janet referring to a bareword) has the following value".
These symbols exist in a look-up table, symbol name -> binding -> value.
When something captures ("sees", etc) a given value, it's actually capturing the binding.
You can see this in the following code (that you now have some experience with):
(def a 1) (defn pa [] (pp a)) # this "captures" the `a` binding (def a 2) (pa) # prints 1
If you wanted to change the visible value, you would need to update the binding, rather than making a new one.
(var a 1) (defn pa [] (pp a)) (set a 2) (pa) # prints 2
The value has notably not changed, as numbers (the values in this case) are immutable.
You were modifying the binding, rather than the value.
You can never use a binding command (e.g def, var, set, etc) to mutate a value, because they modify the binding lookup table, and nothing else.
On the other hand, you can have an immutable binding (def) with mutable data, like an array:
(def a @[1 2]) (defn pa [] (pp a)) (array/push a 3) (pa) # prints @[1 2 3]
This is because pa captured the a binding, which we did not modify or make a new one of (using def, var, or set), but instead we performed operations on the value inside of it.
This kind of comprehension difficulty is present in many languages.
You've probably heard of the never-ending confusion to do with C/C++ pointers and references: it's the same fundamental problem. You can think of C/C++ pointers as successive bindings.
So if you really wanted a book, you could read one on C that tries to explain pointers, and then apply similar principles here, but that's hardly a book "on this".
Since ultimately, these are just principles of software development in general (and, more broadly, of mental models) rather than something more specific.
—
Reply to this email directly, view it on GitHub (#1254 (reply in thread)), or unsubscribe (https://github.com/notifications/unsubscribe-auth/AAAXT7OANROIW3OLFDDIEFDXVJORTANCNFSM6AAAAAA3P6EBS4).
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Is there a reason why Janet works like this? In Common Lisp and Scheme using undefined symbols in function body is fine, the closure has reference to the environment it was created in, the environment can be mutated later with defun/define to add the missing binding. What I really like about Lisps is being able to run a partially written incomplete program. |
Beta Was this translation helpful? Give feedback.
I would normally use
var
andset
for this use-caseYou can make it cleaner by
defn-
ingg
followed by aset
to that private symbol, like so: