IRC channel logs
2025-12-19.log
back to list of logs
<janus>"delete! may modify the structure of lst to construct its return." <janus>what if i want a version of delete that is guaranteed to remove the elements it found? <janus>i must say, i don't understand the value of documentation that leaves behaviour unspecified like this? <janus>over time, surely some people will come to rely on it doing one thing, and others will rely on other behaviour. both might get broken at any time, i assume? <chrislck>janus: because srfi-1 list processing fns *may* modify the in-memory list *vs* creating a brand new list <janus>chrislck: is there an alternative to srfi-1 with completely specified behaviour? <chrislck>it's not idiomatic of functional programming to modify data structure <rlb>janus: right, you just want delete, I suspect, instead of delete! <rlb>delete!'s there for cases where it's OK to mutate, and the implementation may well for performance. <rlb>(but it doesn't *have* to) <janus>i don't really have a problem with mutation in this case <rlb>That's why it's "underspecified", so that an implementation can, and may want to, but isn't required to. <janus>but the issue is that it sounds like delete! isn't actually guaranteed to delete the elements it finds and returns <rlb>But you must always use the result. <rlb>i.e. it may change the structure, but you still want to say (define something (delete! ...)) <rlb>same for filter! and all the other related functions <janus>hmm, not sure i understand. whether delete! mutates the input list depends on whether the output is consumed? <janus>what would even be a way to 'use' delete! without using the result? <janus>like, if i call the function in a begin form, without giving it a name? <rlb>I'm just warning against something I've seen, perhaps from people used to say python or whatever, thinking that you can just say (delete! ... foo) and then use foo. <rlb>instead of (let ((foo (delete! ... foo)) ...) or whatever. <janus>i understand. so you're saying the input is 'invalidated' <rlb>it may or may not be mutated "internally" <janus>right, so you can't depend on it being either way <rlb>It's perfectly fine if you want mutation. <rlb>But in most cases, I'd stick with delete. <rlb>if you want (or the algorithm in question can tolerate) <janus>let's say i execute (let ((x '(1 2 3 4))) (values (partition! x (lambda (x) (< 3 x) x)) <janus>i want it to evaluate to (values '(1 2) '(3 4)) <rlb>I can't recall offhand it it's valid to pass a "const" list to the ! functions. <janus>with 'set!' and 'delete', i think i could write 'partition!' <rlb>i.e. guile may alocate those during compilation to a read-only ELF section. <rlb>so you'd probably want (list 1 2 3 4) there. <janus>oh, i thought it the tick was always the same as (list ...). you're saying it's not? <rlb>I believe not in guile (depending on the specific compilation), and it isn't required to be. <rlb>i.e. I know you can't safely (define x '(1 2 3 4)) (set-car x 5) <janus>right, ok. i will have to look into that <rlb>it may crash at runtime (after compilation) because .go files *are* elf files, and are mmapped into a read-only section. <rlb>(the "constant" data) <rlb>Which of course can be a notable win, performance-wise. <rlb>But sorry, was that tangential to your main question? <janus>ok, but let's assume i used the right list construction, then, surely i should be able to define a deterministic version of 'partition!' using 'set!' and folds and such, right? <rlb>Sure (I think) --- the thing you really have to just think carefully about when using the ! functions, is any potential sharing. <rlb>i.e. if someone else passes you *their* list, then you probably don't want to use the ! functions on it directly unless you know they're always passing "ownership" of the list to you. <janus>right, ok. so determinism is orthogonal to mutation. but using mutation wrong gets you non-determinism and worse (segmentation faults and such) <rlb>i.e. they either expect that your function might mutate it, *or* they know they're never going to use it again <rlb>And if your function is named partition! they definitely should expect mutation :) <janus>probably i should just avoid mutation if there are these extra pitfalls <janus>i don't need the performance anyway <janus>i just thought it would be simpler than let-values and such. but it isn't <rlb>I forget what the actual error is for mutation of a read-only data section, but you shouldn't see segfaults for anything else. <janus>i am a bit surprised there isn't terminology in the documentation that just says that the input must never be used again <rlb>I'd definitely avoid the maybe-mutating variants unless you know you need them, though as with "transients" (e.g. clojure and I think now there's a comparable srfi) if the mutation is completely hidden/contained within a function, then that's more reasonable. <janus>yeah, i was thinking of having 'local' mutation <rlb>e.g. for a non-mutating function, you could still take the input (list-copy ...) it and then mutate away to produce the result. <rlb>You'll also see that as a pattern in scheme sometimes when building a list result cheaply via recursion and cons, and then using a final (reverse! result) at the end to flip the pointers/order. <janus>right, but if i copy, i might as well just write a non-mutation version of partition that, using a fold over the list, a pair in the accumulator reconstructs two lists <rlb>possibly --- depends on the algorithm in question, I suspect. <rlb>Though I haven't used them (in scheme) myself yet, possibly see also SRFI-171 (transducers). <rlb>They're commonly used in clj (now that they're available) --- I think they two are similar, but I'm not positive yet, since I haven't used scheme's yet. <rlb>That can save a *lot* of intermediate garbage for compound operations. <rlb>(even more so in clj where you mostly only have persistent (immutable) data structures) <janus>oh, that is neat, i didn't realize they shipped with guile <rlb>yeah, I keep meaning to take a closer look --- though much of my guile-related time for a while has been on the C side... <janus>ok , so it let's me keep writing functional code, but it won't actually have much copying <janus>and there is no explicit mutation <janus>so it's the best of both worlds? <rlb>You can do pretty well, though for some purposes, you really want actual persistent data structures (like clojure's vector, map, list, etc.). Though as long as you don't mutate, for lists, scheme lists are fine. <rlb>We also have srfi-197 now (in 3.0.11) in case that's useful, and we already had srfi-26 if you haven't seen it. <rlb>(wrt more functional approaches) <janus>srfi-26 is that because i wrote the explicit lambda above? <rlb>oh, no, I was just thinking through various more functional support srfis... <rlb>Was just a general comment. <janus>well i didn't know about it, thanks for the tip. i am still a bit of a beginner <janus>but if i wanted persistent datastructures with guile, is there a library for that? <rlb>I'll have to defer to others on that front --- I think there are some, but I don't have much experience there wrt guile. For some bits I needed, I ended up using Andy's persistent hash map and a by-hand C implementation matching clojure's approach to vectors... <rlb>And at the moment, Andy's vector and map aren't really maintained anywhere (I "vendored" the map and fixed some bugs). <rlb>I think there is some interest in having "something" for guile wrt persistent data structures, but no idea if/when we'll settle on anything. <rlb>Oh, and unfortunately, atm Andy's map has no "delete" operation. <janus>fash looks pretty easy to use <janus>that might be useful. i only knew about the built in hashmap <janus>good to know about the available options <rlb>For now, I might stick to the built-in hash tables if you can, relatively easily. <rlb>i.e. if you don't need a lot of finer-grain concurrency or sharing, et.c <janus>my main concern with non-local mutation is not with concurrency or sharing, it's that i might just forget that i have this state lying around that needs to be 'reset' <janus>basically what the haskellers call 'referential transparency' <janus>if i have a hash-table, and everything becomes mutating or dependent on evaluation order, i am worried it would color the whole code base <janus>so maybe 'vendoring' fash isn't as bad in comparison <janus>but for now, i just use alists though they do seem very primitive <rlb>I believe there's another option or two, but don't recall --- I'm pretty sure hoot has something, for example, but can't recall if it's "readily available" for independent use. <rlb>For fash to be right for what I need, I need a "dissoc" (i.e. delete), but I haven't really tried to add one yet. <rlb>For now lokke does something sketchy -- i.e. delete just sets the value to a unique, private "I'm dead" value... <rlb>janus: hmm, might also want to search https://srfi.schemers.org/ for "immutable" --- looks like there are a number of other potential relevant bits, and all of them should have reference implementations. <old>Is there a way in a syntax-case, to leak a syntax object as a value that would be used at runtime? <old>Hm I guess it's possible by doubling doubling the syntax <mwette>'(a b c) is a literal list, like "abc" is a literal string; I don't think python has literal lists, but it has literal strings. You can't do "abc"[1] = 'z' in python. <ArneBab>if you partition, see the example with receive (or try match): (import (ice-9 receive)) (receive (odds evens) (partition odd? '(1 2 3)) display odds) <ArneBab>(list-set! '(1 2 3) 1 'a) fails since Guile 3, because '(1 2 3) is a literal list. <janus>ice-9 receive, is that like srfi-11? <janus>somehow googling 'ice-9 receive guile' doesn't find that page on the first page <ArneBab>I’ve mostly switched to just using info (texinfo), either on commandline or in Emacs, and find stuff with quick full-text search. <janus>i am already using match, it seems like it is more powerful than receive <janus>i like match because it looks like what i know from other languages <ArneBab>I usually use let-values instead of receive, slightly different form, same function. <ArneBab>match is pretty neat, yes. But it only works on lists, not on multiple values. <ArneBab>If you want a behavior like match (the function that produces the values coming first), you can just use the standard call-with-values <ArneBab>(call-with-values (λ()(partition odd? '(1 2))) list) <ArneBab>If the λ() around partition annoys you like it does me: define-syntax-rule (pipe-values call rec) (call-with-values (λ() call) rec)) ⇒ (pipe-values (partition odd? '(1 2)) list) <rlb>ArneBab: oh, right, thanks for reminding me --- I used that in lokke earlier on, but at least at the time I had a good bit of trouble with the hamts (bugs or my misunderstandings), and that's when Andy mentioned fash, and I switched. <mwette>(call-with-values (lambda () ...) (match-lambda ((...) ))) <ArneBab>chains (let’s see whether that will work out in court: I think when an LLM takes my AGPL licensed code, all its even slightly related outputs should be AGPL). And: congrats! The award is well-earned! <ArneBab>wingo: Free Software is about defending against one evil: other people casting you in digital chains that you cannot break. It is a necessary condition for a Free Society. Software as a Service enables chains with GPL, but not with AGPL, and Javascript enables chains with lax licensing but not with GPL. <ArneBab>wingo: when you wonder whether Free Software actually achieved something, imagine how evil Palantir would be, if almost all software were proprietary. It wouldn’t be possible for a small company to provide a free replacement, because they couldn’t afford the license cost for all the components. And Palantir would get those components much cheaper -- or own them. <ieure>How can I undefine a previously defined variable? <ieure>I'm only asking because I need to clean up some REPL state, this isn't something I want to use in real code. <ieure>I see there's variable-unset!, but it wants a variable object, which I'm not sure how to get from the symbol it's bound to. <ieure>variable-ref seems like it should do that, but it expects a "box" type, which, I don't know how those work in Guile. <mwette>(module-variable (current-module) 'myvar) I think <ieure>Thanks, that works. Awfully verbose. <mwette>lol - I was just thinking about those who love short symbols and those who love long ones. I'm in the short camp. It came up when I was working with the string utilities. I was yearning for strcmp. <ieure>It's not really about the symbol length, it's that (variable-unset! (module-variable (current-module)) 'some-old thing) is *a lot* of moving pieces which all live in different parts of the manual. <mwette>I sometimes make shortcuts: reverse-list->string => rls <ieure>vs. Emacs Lisp's (makunbound 'some-old-thing) <ieure>I am *not* defending the name "makunbound," which is terrible. But having a single function that does this is very convenient. <Arsen>I mean, why not? for repl use, doing so is just fine <ieure>My beef here isn't that I have no leverage to make things better, it's that they're (IMO) unnecessarily difficult to use in the first place. <ieure>I read multiple parts of the manual, searched the index, etc. and couldn't figure out how to do the fairly simple thing I wanted, even though I've written thousands of lines of Guile at this point. *That* is the thing I'm unhappy about.