There’s a way of handling letrec
with right-hand sides restricted to lambda
expressions without mutation or cyclic data structures. You can change your environment representation to have two kinds of frames: normal and recursive. A normal frame maps variable names to values; a recursive frame maps variable names to lambda
expressions. If you look up a name and find it in a normal frame, you return the value. If you look up a name and find it in a recursive frame, you create and return a closure of the lambda
expression and the environment starting with recursive frame where you found the name.
this does feel like a situation in which nix etc is trying to treat everything like a C program even in cases where that doesn’t make sense
Well, I managed to get it working by having define
only available at the top-level, and pulling it’s evaluation farther up the eval tree than normal. No recursion (we don’t have even conditionals), so all the stuff about closures and stuff is sort of moot.
In spite of that, the following program works in my implementation (yay Church!) define Z
lambda (f)
let (A lambda (x) (f lambda (v) ((x x) v)))
(A A)
define czero lambda (f) lambda (x) x
define cone lambda (f) lambda (x) (f x)
define csucc lambda (n) lambda (f) lambda (x) (f ((n f) x))
define cplus lambda (m) lambda (n) lambda (f) lambda (x) ((m f) ((n f) x))
define cpred lambda (n) lambda (f) lambda (x) (((n lambda (g) lambda (h) (h (g f))) lambda (u) x) lambda (u) u)
define cmult lambda (m) lambda (n) lambda (f) (m (n f))
define c-to-nat
lambda (n)
((n lambda (n) (+ n 1)) 0)
define ctrue lambda (a) lambda (b) a
define cfalse lambda (a) lambda (b) b
define c-to-bool
lambda (b)
((b (= 1 1)) (= 0 1))
define czero? lambda (n) ((n lambda (x) cfalse) ctrue)
define cif lambda (p) lambda (a) lambda (b) ((p a) b)
define !-prime
lambda (f)
lambda (n)
((((cif (czero? n))
lambda (x) cone)
lambda (x) ((cmult n) (f (cpred n))))
;; this last just forces the thunk returned by cif
;; eta-expansion required because otherwise all arguments are fully
;; evaluated
czero)
define ! (Z !-prime)
define c7 (csucc (csucc (csucc (csucc (csucc (csucc cone))))))
(c-to-nat (! c7))
What is more idiomatic/recommended? (map (lambda (task) (task-field task 'completed?))
tasks)
or (for/list ([task tasks])
(task-field task 'completed?))
?
I would use map
if I already have the function to apply. I would use for/list
if the body is a more complex expression. In this case neither of these applies. :slightly_smiling_face:
Another question: I want an expression that returns #t
for any “truthy” value and #f
otherwise. I can do that with (not (not v))
. Is there a more straightforward way?
I almost always end up using for/* for whatever I’m doing. I personally think it is more readable and it leaves more room for extension.
I can think of (if v #t #f)
or (and v #t)
; only the first is immediately clear without additional reasoning.
@dyllongagnier Would you also use for/fold
in the above case if the map
where filter
? Or is there another for
idiom for that?
(and v #t)
is the more-usual way in Racket
@sschwarzer you can use #:when
in the for/list
to include a filter
Ok, so (and v #t)
is actually idiomatic?
yes
Hm, just thinking about it, wouldn’t it be natural for (and v #t)
to expand to (if v #t #f)
?
it does expand to that
@samth Right, nice. :slightly_smiling_face: The for
forms have so many options that I sometimes miss the forest for the trees.
I hope I never have to adapt my Racket code to a Scheme. :satisfied:
Do you have recommendations for well-written Racket code I could read to get a better understanding of design approaches and idioms? The background of my question is that I’m rather new to Racket and to Lisp/Scheme and sometimes I wonder if I’m doing things in unneccesarily complicated ways.
The style guide has some examples I know it’s not quite what you are asking for but it helps me https://docs.racket-lang.org/style/Units_of_Code.html\|https://docs.racket-lang.org/style/Units_of_Code.html
Yes, I’ve read the style guide and found it useful. :slightly_smiling_face: I guess I’m now looking for a higher-level design help. Maybe more like patterns than idioms (although I expect “patterns” in Racket to be much more simple than in languages like Java).
I remember I had started with “How to design programs” a while ago, but lost interest because • It seems targeted at programming beginners and I found it progressed too slowly. • I didn’t want to learn the beginner languages and then “unlearn” them and after that learn Racket. I wanted to learn Racket directly.
@salkvus has joined the channel
(I do have a lot of experience in imperative programming, especially OOP. So I’m looking for FP/Lisp/Scheme/Racket-specific design advice, not so much for “paradigm-independent” advice.)
It sounds like I was similar to you when I first learned Racket. I think, for me, “all the parentheses” was not so big a deal. Breaking my habit of mutating objects was harder, at first. Especially on a “local” or “tactical” level. Just things like, “well I need to loop” and having to stop myself from using set!
and instead using map
or for/xxx
or whatever. But I see you’re already focusing on those, so great! I think from there it gets easier thinking in terms of composing functions — “chains” of functions that accept and return values. Instead of functions that poke some data structure. You may just need some time to rewire your brain, how you approach things.
Another “idiom” you might find useful is pattern matching. Pretty frequently you’ll have a function that returns a value. You need to test the value, e.g. do different things if it’s false or some other value. And you also want to bind the value to a variable, to use further. Using match
for that is useful. You may find the doc page a little daunting at first — there are so many options! — but with a little time it will make sense and feel comfortable.
(You can also just bind a value to variable with define
or let
, and then use if
or cond
as a separate step. But doing it in one swell foop can make certain code cleaner. In most people’s opinion, AFAIK.)
Well, if there’s a repeating pattern, that’s often replaced with a macro :slightly_smiling_face: That’s why lisp/scheme/racket code is compact, since it can push DRY pretty far.
So i guess my advice would be to learn macros when you feel ready
Many, many thanks for all your replies to my code/design questions! :heart: I’ll give some answers in the threads.
I read the chapter on macros in the Racket Guide and I read Greg’s (great!) tutorial on macros. I’ll look into using macros when I think it might be appropriate. :slightly_smiling_face: Maybe I’m not ready yet to see when a macro would be the best solution. It’s a hint if the code is repetitive, but if my problem isn’t repetition or if I don’t see the repeating pattern, macros won’t help. :wink:
Fortunately, I got (somewhat) used to the functional style a few years ago when I looked into Haskell for a bit. This stumped me at first. With my over 30 years of programming experience, I felt like a programming beginner. :satisfied: Since I looked into Haskell, I wanted to do more with FP, but it kinda fell by the wayside for several years.
Regarding pattern matching: I used it in Haskell and like it. In Racket I kind of rather avoided it so far because at the moment I write smallish command line programs and when I require racket/match
this about doubles the startup time. (I wrote about this further up in the channel.)
By the way, many thanks for your macro tutorial https://www.greghendershott.com/fear-of-macros/ . :+1: I read it recently (but haven’t written any macros yet). I worked a bit with macros in Nim, though.
Regarding define
and let
: Yes, I use them sometimes as “pseudo-imperative” code. I can see the order of “steps” more easily. I follow the Racket style guide and use rather define
than let
if possible.
Here’s an example of what I was/am struggling with today. I didn’t post it earlier because I was still working on it: https://git.sr.ht/~sschwarzer/todoreport-racket/tree/364b75bd493a8544a3e2a5b55098e4fda21a7bb9/item/task.rkt#L325 To abstract the handling for different kinds of task fields (see https://github.com/todotxt/todo.txt#todotxt-format-rules ), I created a function which creates two functions depending on the field type. (I’d say this is conceptually similar to OOP where I would maybe use three classes with two methods each.) My approach seems to work ok, but I wonder if that’s the way to go since the function grouping-funcs
is deeply nested. Then, I have the impression you can get away with deeper nesting in functional code than in imperative code. :slightly_smiling_face:
When I went to @mflatt’s class on HtDP for teachers back in 2007, even though I’d known about DrScheme for years already, and Scheme for more years, and Lisp since about 40 years ago (early college), I’d never been presented any style information, it was mostly “do what other people do, see if you can improve on it.” When I saw the HtDP patterns, my immediate reaction was “why didn’t anyone show me this in 1980!” I guess because it didn’t exist. But so many things people do all the time are covered by HtDP patterns. I get what you say about it being oriented to beginners, at least the early material. I think it’s kind of perfect for college students new to programming. It just didn’t exist when I was in college, and I already knew BASIC Assembly Forth Fortran APL and I’d even implemented a few languages of my own even though I had no idea what I was doing. In a way, that one week class in 2007 was perfect for me to “get it.” Those “Teach Scheme!” classes aren’t run any more, I don’t think, but they were great. I suppose these days you could make a series of YouTube videos that ran experienced programmers through the fundamentals of HtDP.
Overall, I find FP very intriguing. :slightly_smiling_face:
Many thanks. So I guess I’ll have another look at HtDP. :slightly_smiling_face:
That project looks pretty interesting. I wonder what the Emacs org-mode people would think of it.
The todo.txt on Github isn’t my project. I’m merely writing a Racket program (the Sourcehut link) to process these todo.txt format files. :slightly_smiling_face:
Ah, you said that project, not your project.
I wasn’t sure if we had a misunderstanding.
I looked at your code. If you’re new to Racket, I think you’re off to a great start. It is very readable. You have comments, OMG. Structurally what you’re doing makes sense to me. I guess the function you think is very nested doesn’t look that nested to me. The number of things you’re doing is finite and small. It’s not like you’re juggling two dozen things. Maybe 3 balls, not 50. So the nesting doesn’t bother me at all. I see bunches of lambdas and I think “that’s a little unusual” but there’s nothing at all wrong with it. I guess in other people’s code I might expect to see those things broken out into small functions of their own that you could call from the bigger function. But if you’re only doing those things in that one place, having those very local functions is fine (to me). I can see what’s going on.
If I really wanted to get rid of the local lambdas, I would turn those closures into top-level functions.
@gknauth Wow, thanks for the great feedback. I’m somewhat relieved that you say I’m on a good path.
By the way, Jerry Sussman told me his newest book is coming out next week so I ordered it yesterday. I don’t know if it’s appropriate to your questions here but from the title and the description I read and what he told me, it might be, once you really want to feel great about your code. https://www.amazon.com/Software-Design-Flexibility-Programming-Yourself/dp/0262045494/
And then use a hash table to dispatch functions
I’ve seen some ugly code in my life and your code is anything but. No, you’re doing great.
Regarding the comments, that’s something that’s important to me. I actually gave a talk on that subject in 2019: https://sschwarzer.com/download/comments_pycon_de2019.pdf
But your code is absolutely comprehensible to me. I wouldn’t worry about the extensibility for the time being
The only problem I have with comments is when they are not maintained, they are next to useless. At work I sometimes have to look at Java comments that were written, beautifully, in 2005 or 2006, but the comments have very little to do with what the code does the last time someone touched it. So by habit I look at code first to see what’s happening, and I only look at comments if I can’t make sense of the code.
I’m looking forward to that one. I may wait for a 2nd printing to handle some errata since I’m way behind on reading anyway :)
I try my best to keep comments up to date. :slightly_smiling_face: My approach is that still comments can get out of date, but the advantages outweigh the disadvantages. (If the comments are usually written well.)
By the way, your mention of Forth gives me a nice nostalgic feeling. :slightly_smiling_face: I came across Forth in the 1980s and liked it a lot. A very simple, yet very flexible language (like Scheme).
I remember I wrote a DSL in Forth for horizontal and vertical text UI menus.
Thank you! :slightly_smiling_face:
I was lucky to attend Chuck Moore’s talk in person: https://www.infoq.com/presentations/power-144-chip/
Cool :slightly_smiling_face:
By the way, I think “Thinking Forth” was the first book on software design that I read.
Do Racket’s universal and existential contracts have the same expressive power as the universal and existential types from the static typing world?
There’s substantial literature on this; I think the shortest answer is that it’s complicated
See, for example, Blame for All, POPL 2011 and all the papers that cite it subsequently
Note that that isn’t about exactly what Racket does, and comparing types to contracts is far from precise anyway
See also Arjun Guha’s 2008 paper which is about what Racket does, but is less formal
I’m recently learning Forth. It’s one of those few languages with powerful macro ability like Lisp/Scheme.
It’s very fun but also very obfuscating, LOL!
Thanks for the info!
And Forth was indeed influenced by Lisp. https://news.ycombinator.com/item?id=18228381\|https://news.ycombinator.com/item?id=18228381