laurent.orseau
2021-3-25 07:09:20

Personally i wouldn’t use continuations for this—or for search algorithms in general. A for*/fold would be best for a fixed number of variables. Otherwise a named let loop, and a for over variables would be my choice. It could be purely functional with an accumulator, but if i needed to track many things then using set! could be simpler.



chansey97
2021-3-25 09:18:53

@laurent.orseau Do you mean list-comprehension? Like this: (for*/list ([b1 '(#t #f)] [b2 '(#t #f)] [b3 '(#t #f)] #:when (or (and (not b1) b2) (and b1 (not b3)))) (list b1 b2 b3)) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f)) Indeed there are many ways to achieve this kind of nondeterministic computation. But my question is mainly about using delimited continuation. When we use delimited continuation (e.g. reset/shift), it seems always to cooperate well with mutation.


laurent.orseau
2021-3-25 09:27:23

Someone please correct me if I’m wrong, but I’m not sure I would call continuations purely functional. So if your goal is to be more functional, list comprehension or name let loops would be the way to go. If your goal is to have the most ‘elegant’ solution (for some subjective definition of elegant) with the constraint of using continuations, then I’m probably not the right person to answer—I find the control flow of continuations somewhat difficult to keep track of in the general case. If your goal is to tackle SAT problems, then clearly continuations is the wrong tool, unless you care only about small/toy problems.


chansey97
2021-3-25 09:28:12

Comparing with for*/list solution, the rest/shift version is something like: (define (flip lst) (shift c (map (λ (x) (c x)) lst))) (let ((acc-lst '())) (reset (let ((b1 (flip '(#t #f))) (b2 (flip '(#t #f))) (b3 (flip '(#t #f)))) (when (or (and (not b1) b2) (and b1 (not b3))) (set! acc-lst (cons `(,b1 ,b2 ,b3) acc-lst))) )) (reverse acc-lst)) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f)) They are not very different in syntax, but the rest/shift version use mutation. (I’m sorry, this may not be a formal question. I’m just curious about this phenomenon and hope some comments:sweat_smile:. )


sorawee
2021-3-25 09:34:32

I consider continuation to still be in the realm of functional programming. However, it is a powerful construct, and as a result, it makes program reasoning difficult, which kinda kills “functional programming is easy to reason”.


laurent.orseau
2021-3-25 09:53:56

It’s absolutely fine :slightly_smiling_face: Although I wouldn’t really tag this question ‘beginners’ :wink: It’s just that I’m unclear what kind of feedback you’re looking for. You say: > These two solutions return the same result, but which one is better? but there are lots of possible definitions of ‘better’, as I hinted at in my previous reply: speed efficiency, memory efficiency, readability, maintainability, compatibility, safety, and all possible dogmatic philosophies about programming styles.

That’s why I’m asking about your goal instead: Do you want to solve SAT problems, do you want to learn to use continuations, do you want to understand a particular point about continuations and mutation, do you want to share your code with other people, …?


soegaard2
2021-3-25 09:56:52

@chansey97 If you are annoyed by the set! you could always hide in a macro :wink: The amb construct from SICP is an example of such “hiding”.


chansey97
2021-3-25 10:01:03

> 1. tag this question ‘beginners’ > 2. These two solutions return the same result, but which one is better? Yes, this is my fault. PS: I am a beginner to reset/shift, recently I was trying to use reset/shift and observed some phenomenon. The real question may be “understanding a particular point about continuations and mutation”.


laurent.orseau
2021-3-25 10:09:16

If you refactor your Solution–2 with intermediate (reusable) constructs with good names then you may find your main test-sat as readable as Solution–1 I guess. Continuation-based accumulator?


chansey97
2021-3-25 10:13:45

Hiding in a macro is a great idea. This allow us to create own for*/list by using rest/shift construct and mutation. In the past, this kind of list comprehension need list monad (or applicative), but rest/shift gives us a new point.


chansey97
2021-3-25 10:20:31

> refactor your Solution–2 with intermediate (reusable) constructs with good names I’m not very understand. Could you explain further? Thanks.


laurent.orseau
2021-3-25 10:40:51

I mean that you can also hide your structs and their usage with macros, and maybe split/extract some code from flip to make it more readable and look more ‘generic’ (that is, you’re building reusable tools, and thus these tools)


chansey97
2021-3-25 13:55:35

Solution–3 (functional and intuitive way, no need mutation!): (define (flip) (shift c (begin (c #t) (c #f) (void) ))) (define (test-sat) (let ((g (generator () (reset (let ((b1 (flip)) (b2 (flip)) (b3 (flip))) (let ((r (or (and (not b1) b2) (and b1 (not b3))))) (if r (yield `(,b1 ,b2 ,b3)) #f)) ))))) (for/list ([i (in-producer g (void))]) i))) (test-sat) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f)) We can compose generator and reset/shift.


soegaard2
2021-3-25 13:57:56

Very nice!


chansey97
2021-3-25 14:40:36

If we introduce generator then the reset/shift is not necessary (In fact, the reset/shift is meaningless from the beginning). (define (test-sat_2) (let ((g (generator () (for ([b1 '(#t #f)]) (for ([b2 '(#t #f)]) (for ([b3 '(#t #f)]) (when (or (and (not b1) b2) (and b1 (not b3))) (yield `(,b1 ,b2 ,b3))))))))) (for/list ([i (in-producer g (void))]) i))) (test-sat_2) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f)) omg…what am I doing?


soegaard2
2021-3-25 14:41:48

Instead of generator+reset/shift, can you do it with lambda+reset/shift?


chansey97
2021-3-25 14:42:29

What do you mean by “do it with lambda+reset/shift”?


chansey97
2021-3-25 14:58:12

The 3SAT problem may be too simple to show the expressiveness of reset/shift .


soegaard2
2021-3-25 15:04:03

I suspect generator could be defined in terms of lambda and reset/shift .


chansey97
2021-3-25 15:04:37

I agree. Another way is using call/cc.


chansey97
2021-3-25 15:49:51

Also this 3SAT problem can be solved easily by imperative language: public static List<Tuple<bool, bool, bool>> SAT() { var resList = new List<Tuple<bool, bool, bool>>(); foreach (var b1 in new List<bool> { true, false }) { foreach (var b2 in new List<bool> { true, false }) { foreach (var b3 in new List<bool> { true, false }) { if ((!b1 && b2) \|\| (b1 && !b3)) { resList.Add(new Tuple<bool, bool, bool>(b1, b2, b3)); } } } } return resList; }


ben.knoble
2021-3-25 18:00:05

Question: are any of these (automatically or easy to make) parallel? For something boolean satisfiability, that might be something to consider (rather than just “try all combinations one at a time”)


sorawee
2021-3-25 18:02:35

SAT solving is known to be very hard to parallelize. IIUC, the most commonly used strategy is to solve the same problem in parallel, but with different configuration (like random seed, etc), and answer the one that finishes first.


ben.knoble
2021-3-25 18:20:43

interesting, thanks, didn’t know that. Should’ve figured you would know; I’m citing your Rosette-related paper in a report for my MS :slightly_smiling_face:


sorawee
2021-3-25 18:23:42

Glad to hear! :slightly_smiling_face:


raoul.schorer
2021-3-25 21:02:37

Is it possible to make a predicate associative? that takes a binary operator, e.g. returns #t on + and #f on -? I suspect that’s not the case in general since: 1. associativity is defined for an operation on a set, so it requires that we specify the set 2. associativity testing would require 3 arguments So essentially, I’d have to make a catalogue if I want to test that, e.g. an assoc such as '((+ natural?) (* natural?) ...) ?


kellysmith12.21
2021-3-25 21:12:17

@raoul.schorer Associativity of operators has to be mathematically proven. This can be done with a program, but it requires a theorem-proving language. Although automated proof generation has come a long way, I suspect it’s not (currently) possible to simply ask if an operator is associative and get a yes-or-no answer.


kellysmith12.21
2021-3-25 21:15:37

You could try QuickCheck-style property testing, but that 1. Doesn’t give a definitive answer. 2. Is computationally-intensive and not suited for use in the middle of a program.


kellysmith12.21
2021-3-25 21:17:57

(Also note that, since Racket’s arithmetic operators also work on floating-point numbers, they don’t have the nice algebraic properties we’d expect, in the general case.)


raoul.schorer
2021-3-25 21:21:30

@kellysmith12.21 Yes, referring to the docs I guess contract random generation yields QuickCheck testing. Catalogue it is, then. Thanks.


chansey97
2021-3-26 00:29:08

I wrote a simple generator by reset/shift. For simplicity, I did not save a local state c inside the generator but pass the c to caller explicitly. (define (test-sat) (define (make-g) (reset (begin (shift c `(init ,c)) (for ([b1 '(#t #f)]) (for ([b2 '(#t #f)]) (for ([b3 '(#t #f)]) (if (or (and (not b1) b2) (and b1 (not b3))) (shift c `(running (,c (,b1 ,b2 ,b3)))) #f)))) (shift c '(done))))) (define (g->list g) (match g [`(init ,c) (g->list (c #f))] [`(running (,c ,v)) (cons v (g->list (c #f)))] [`(done) '()])) (g->list (make-g)) ) (test-sat) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f)) It works.

However this approach does not seem to cooperate well with the old flip code because reset conflict. See following code: (define (flip) (shift c (begin (c #t) (c #f) (void) ))) (define (test-sat) (define (make-g) (reset ; <---- reset-1 (begin (shift c `(init ,c)) (reset ; <---- reset-2 (let ((b1 (flip)) (b2 (flip)) (b3 (flip))) (if (or (and (not b1) b2) (and b1 (not b3))) (begin (println `(,b1 ,b2 ,b3)) (shift c `(running (,c (,b1 ,b2 ,b3))))) ; <---- Conflict! We should shift to reset-1 instead of reset-2 #f) )) (shift c '(done))))) (define (g->list g) (match g [`(init ,c) (g->list (c #f))] [`(running (,c ,v)) (cons v (g->list (c #f)))] [`(done) '()])) (g->list (make-g)) ) (test-sat) ; => '() Fortunately, Racket supports multiple prompts: (define p1 (make-continuation-prompt-tag 'reset-1)) (define p2 (make-continuation-prompt-tag 'reset-2)) (define (flip) (shift-at p2 c (begin (c #t) (c #f) (void) ))) (define (test-sat) (define (make-g) (reset-at p1 ; <---- reset-1 (begin (shift-at p1 c `(init ,c)) (reset-at p2 ; <---- reset-2 (let ((b1 (flip)) (b2 (flip)) (b3 (flip))) (if (or (and (not b1) b2) (and b1 (not b3))) (begin (println `(,b1 ,b2 ,b3)) (shift-at p1 c `(running (,c (,b1 ,b2 ,b3))))) #f) )) (shift-at p1 c '(done))))) (define (g->list g) (match g [`(init ,c) (g->list (c #f))] [`(running (,c ,v)) (cons v (g->list (c #f)))] [`(done) '()])) (g->list (make-g)) ) (test-sat) ;; => '((#t #t #f) (#t #f #f) (#f #t #t) (#f #t #f))