mk-convert Contents 1 Converting to minikanren, quasimatically. 08 July 2014

Similar documents
PROFESSOR: Last time, we took a look at an explicit control evaluator for Lisp, and that bridged the gap between

6.001 Notes: Section 15.1

6.001 Notes: Section 8.1


Intro. Scheme Basics. scm> 5 5. scm>

Scheme Basics > (butfirst '(help!)) ()

In our first lecture on sets and set theory, we introduced a bunch of new symbols and terminology.

Project 5 - The Meta-Circular Evaluator

6.001 Notes: Section 17.5

6.001 Notes: Section 6.1

Project 5 - The Meta-Circular Evaluator

Linked Lists. What is a Linked List?

Instructor: Craig Duckett. Lecture 03: Tuesday, April 3, 2018 SQL Sorting, Aggregates and Joining Tables

MITOCW watch?v=0jljzrnhwoi

Formal Methods of Software Design, Eric Hehner, segment 24 page 1 out of 5

MITOCW watch?v=yarwp7tntl4

Announcements. The current topic: Scheme. Review: BST functions. Review: Representing trees in Scheme. Reminder: Lab 2 is due on Monday at 10:30 am.

Lisp. Versions of LISP

PROFESSOR: Well, yesterday we learned a bit about symbolic manipulation, and we wrote a rather stylized

CPSC 320 Sample Solution, Playing with Graphs!

CS61A Summer 2010 George Wang, Jonathan Kotker, Seshadri Mahalingam, Eric Tzeng, Steven Tang

MITOCW MIT6_01SC_rec2_300k.mp4

Intro. Speed V Growth

CS3 Midterm 1 Fall 2006


MITOCW watch?v=zm5mw5nkzjg

PROFESSOR: So far in this course we've been talking a lot about data abstraction. And remember the idea is that

Remember, this question was mis-worded: you could also add quoted words and sentences in the blanks below. This allowed for a solution to [4] below.

CSE 341 Section Handout #6 Cheat Sheet

Formal Methods of Software Design, Eric Hehner, segment 1 page 1 out of 5

MITOCW ocw f99-lec07_300k

CS 360 Programming Languages Interpreters

Instructor: Craig Duckett. Lecture 04: Thursday, April 5, Relationships

MITOCW watch?v=flgjisf3l78

MITOCW watch?v=rvrkt-jxvko

Typed Racket: Racket with Static Types

(Refer Slide Time 6:48)

Guide to First-Order Logic Translations

A PROGRAM IS A SEQUENCE of instructions that a computer can execute to

User-defined Functions. Conditional Expressions in Scheme

CSE413: Programming Languages and Implementation Racket structs Implementing languages with interpreters Implementing closures

A lot of people make repeated mistakes of not calling their functions and getting errors. Make sure you're calling your functions.

6.001 Notes: Section 4.1

Repetition Through Recursion

6.034 Artificial Intelligence, Fall 2006 Prof. Patrick H. Winston. Problem Set 1

MITOCW ocw apr k

MITOCW watch?v=9h6muyzjms0

MITOCW watch?v=kz7jjltq9r4

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

COMP 105 Homework: Type Systems

Write a procedure powerset which takes as its only argument a set S and returns the powerset of S.

STREAMS 10. Basics of Streams. Practice with Streams COMPUTER SCIENCE 61AS. 1. What is a stream?

Azon Master Class. By Ryan Stevenson Guidebook #5 WordPress Usage

6.001 Notes: Section 5.1

Programming Project #4: A Logo Interpreter Summer 2005

Naming Things in Adafruit IO

ENVIRONMENT MODEL: FUNCTIONS, DATA 18

The following content is provided under a Creative Commons license. Your support

Hi everyone. Starting this week I'm going to make a couple tweaks to how section is run. The first thing is that I'm going to go over all the slides

The Typed Racket Guide

MITOCW mit-6-00-f08-lec16_300k

CSE 413 Languages & Implementation. Hal Perkins Winter 2019 Structs, Implementing Languages (credits: Dan Grossman, CSE 341)

PROFESSOR: Well, now that we've given you some power to make independent local state and to model objects,

Racket. CSE341: Programming Languages Lecture 14 Introduction to Racket. Getting started. Racket vs. Scheme. Example.

Title: Recursion and Higher Order Functions

CS61A Notes Week 1A: Basics, order of evaluation, special forms, recursion

The following content is provided under a Creative Commons license. Your support

CS61A Notes Disc 11: Streams Streaming Along

CS61A Discussion Notes: Week 11: The Metacircular Evaluator By Greg Krimer, with slight modifications by Phoebus Chen (using notes from Todd Segal)

The following content is provided under a Creative Commons license. Your support

Well, I hope you appreciate that we have inducted you into some real magic, the magic of

Building a system for symbolic differentiation

How to Improve Your Campaign Conversion Rates

CS115 - Module 10 - General Trees

Close Your File Template

Skill 1: Multiplying Polynomials

Well, Hal just told us how you build robust systems. The key idea was-- I'm sure that many of

It Might Be Valid, But It's Still Wrong Paul Maskens and Andy Kramek

Problem One: A Quick Algebra Review

Programming Languages. Thunks, Laziness, Streams, Memoization. Adapted from Dan Grossman s PL class, U. of Washington

6.001 Notes: Section 1.1

Week - 04 Lecture - 01 Merge Sort. (Refer Slide Time: 00:02)

contain a geometry package, and so on). All Java classes should belong to a package, and you specify that package by typing:

MITOCW watch?v=hverxup4cfg

MITOCW watch?v=4dj1oguwtem

(Provisional) Lecture 08: List recursion and recursive diagrams 10:00 AM, Sep 22, 2017

Eventually, you'll be returned to the AVD Manager. From there, you'll see your new device.

Fall 2018 Discussion 8: October 24, 2018 Solutions. 1 Introduction. 2 Primitives

(Refer Slide Time: 00:51)

Shared Variables and Interference

Y-Combinator. Contents. 1 Introduction. Avi Hayoun, Hodai Goldman and Lior Zur-Lotan. November 24, Terms recap. 1.2 Y-Combinator notes

CSE341: Programming Languages Lecture 15 Macros. Zach Tatlock Winter 2018

What is a macro. CSE341: Programming Languages Lecture 15 Macros. Example uses. Using Racket Macros. Zach Tatlock Winter 2018

Scheme: Data. CS F331 Programming Languages CSCE A331 Programming Language Concepts Lecture Slides Monday, April 3, Glenn G.

Bindings & Substitution

The following content is provided under a Creative Commons license. Your support

The Stack, Free Store, and Global Namespace

Computer Science 21b (Spring Term, 2015) Structure and Interpretation of Computer Programs. Lexical addressing

CPSC 320 Notes: What's in a Reduction?

Denotational semantics

Transcription:

mk-convert 08 July 2014 Contents 1 Converting to minikanren, quasimatically. 1 1.1 Variations on a Scheme..................... 2 1.2 Racket to minikanren, nally.................. 8 1.3 Back to the beginning...................... 11 1.4 Conclusions and further guidance................ 12 1 Converting to minikanren, quasimatically. #lang racket (require utah/mk) (require utah/let-pair) (provide (all-defined-out)) Some folk wanted a bit more detail on converting programs from Scheme to minikanren. So I thought we could walk through an example in some detail, and that way you can play along at home. So here's a real humdinger of an examplelist-union. It takes two lists (which represent sets) and returns a list representation of their union. ((null? s1) s2) ((member (car s1) s2) (list-union (cdr s1) s2)) (else (cons (car s1) (list-union (cdr s1) s2))))) Let's give it a whirl 1

-> (list-union '() '(1 2 3)) (1 2 3) -> (list-union '(2) '(3 2 1)) (3 2 1) -> (list-union '(1 2) '(4 5)) (1 2 4 5) That seems to work, ship it. So, as Will said on Tuesday, my preferred way to do this is to get my Scheme program looking as much like the resultant minikanren as I can, and then convert to minikanren from there. Testing the whole way through. So the plan is to massage this code until it looks like the minikanren would just fall out trivially, and see if we can't list some rules along the way. 1.1 Variations on a Scheme Let's start by talking about the question of that rst cond line, (null? s1). We know, where we're going, that == is our big, giant hammer. Where it makes sense, I'd like to use equal?, the Racket primitive which tests for structural equality, because that's going to map pretty well to ==. So let's rewrite that to a similar question, which maps over nicely. ((member (car s1) s2) (list-union (cdr s1) s2)) (else (cons (car s1) (list-union (cdr s1) s2))))) Looking good there. The answer of that rst cond clause is a value. We know that when we go to minikanrenize this code, we'll just unify that value with the out variable we will introduce. Now, something to bear in mind is that you can't rely on any Scheme primitives aside from cons. We can cons things together to build structures, but we don't get to pull'em apart any way other than unifying with a pair. In the remaining lines, we're taking cars and cdrs, we'll need some way to represent what we're doing. And here's another minikanrenizing guideline. When you have multiple cond clauses which require performing the same structural decomposition, only do it once, and then use it. Right quick we're going to do a couple of steps that at rst blush look like some pretty fancy dancing. But after doing it a few times, it'll get to be old hat. 2

((member (car s1) s2) (list-union (cdr s1) s2)) (else (cons (car s1) (list-union (cdr s1) s2))))))) In either one of those last two lines, s1 has to be a pair. So that (pair? s1) question we ask there is safe and valid and right and good. And now that we know it's a pair, we have the opportunity to let-bind the car and cdr of s1, and use those names throughout. So here we go. (let ((a (car s1)) (d (cdr s1))) ((member a s2) (list-union d s2)) (else (cons a (list-union d s2)))))))) That's an absolutely legit maneuver. And if you're happy with doing it that way, power to you. I'm going to use a macro we put together called let-pair to do something similar. (define-syntax let-pair (syntax-rules () [(_ ((a. d) call) body) (let ((tmp-res call)) (let ((a (car tmp-res)) body))])) (d (cdr tmp-res))) Let-pair evaluates the expression (which better be something with a car and cdr), and then binds the car and cdr to names you give it. It's just a bit of syntax wizardry, but compare the below to the above and you'll get what's going on. 3

((member a s2) (list-union d s2)) (else (cons a (list-union d s2)))))))) I like that slightly better, `cause when we go to minikanren, we just slip an == in there with some backticks and commas and we'll be on our way. Nooow we get to something pretty interesting. That call to member. We can't rely on Racket's member, because it doesn't know how to go backwards. We're going to have to write our own function, that way we can minikanrenize it, and make our list-union do what we want when we go to list-uniono. So, actually, we're at an interesting point here. A small aside with a punchline. -> (member 3 '(4 5 6)) #f Above, the expression evaluates to #f because 3 isn't in that list. Do you know what value the expression below will evaluate to? -> (member 3 '(3 4 5)) Give you a hint, it's not #t like you might expect. In fact, it's (3 4 5) -> (member 3 '(3 4 5)) (3 4 5) When we write our version of this function, we've got some decisions to make. We must decide if we want to preserve that behavior, or just return #f or #tthat is, should we write a predicate member? instead. And bear in mind that else in the nal line below still has to be dealt with as well. 4

((member a s2) (list-union d s2)) ---> (else (cons a (list-union d s2)))))))) Now, here's the deciding factor. When member returns a non-false value, like (3 4 5) above, we don't care what it is. Sometimes, you might. Here we don't. We'll go with the predicate, member?. (define (member? x ls) ((null? ls) #f) ((equal? x (car ls)) #t) (else (member? x (cdr ls))))) ((member? a s2) (list-union d s2)) (else (cons a (list-union d s2)))))))) And we can go ahead and test that, make sure it still works. Check. Now, let's do to member? the sorts of things we've done to list-union so far. (define (member? x ls) ((equal? '() ls) #f) ((pair? ls) (let-pair ((a. d) ls) ((equal? x a) #t) ---> (else (member? x d))))))) 5

((member? a s2) (list-union d s2)) (else (cons a (list-union d s2)))))))) Will made a point of demonstrating that we need our cond clauses to be non-overlapping to get the behavior we expect and desire when running backwards. Recall that in the rembero example we had to add disequality constraints in order to avoid the odd-looking answers. Same deal here. Now, the else sitting in member? is one we know how to deal with. I know how to write not equal?, and that'll translate right nicely to minikanren. (define (member? x ls) ((equal? '() ls) #f) ((pair? ls) (let-pair ((a. d) ls) ((equal? x a) #t) ((not (equal? x a)) (member? x d))))))) ((member? a s2) (list-union d s2)) (else (cons a (list-union d s2)))))))) Back to list-union. That else in member? was pretty easy to do. The one in list-union, not so much. The fall-through cases of a cond clause with a non-trivial question (that is, where the question of the cond line isn't going to be just a simple == thing) are non-trivial. We could write something called not-a-member, that returns true in all the cases that member will return false. That is, we construct a predicate that's the complement of member?, and use it where we have the else. Or we can use the boolean value which is the result of the call to member?. This decision impacts how we translate member? to minikanren. I'm going to choose to make use of the boolean value which we get back. So we'll rewrite that inner cond, and let-bind the result of the call. 6

(let ((b (member? a s2))) ((equal? b #t) (list-union d s2)) (else (cons a (list-union d s2))))))))) Now we've got something we can do with that else. If your instict was to jump to disequality constraints, you aren't wrong, but we can do better. If it isn't #t, then it must be #f. (let ((b (member? a s2))) ((equal? b #t) (list-union d s2)) ((equal? b #f) (cons a (list-union d s2))))))))) Now I want to look at those two answers of the nal two cond clauses. Here there is something interesting. We are computing the same value, (listunion d s2), either way. We can pull that up into a let binding. We can in fact pull that up into the let binding we've already created. This implies that the order in which we evaluate the clauses of that let doesn't matter. In the biz, we use `res' as a name for the variable in the recursive call, for the RESult of the recursion. (let ((b (member? a s2)) (res (list-union d s2))) 7

((equal? b #t) res) ((equal? b #f) (cons a res)))))))) Now (cons a res) is a value, which when we go to minikanren, we'll just unify with out. I'm going to go ahead and rewrite it with the backtick comma syntax, and then we can marvel at what we've done. (define (member? x ls) ((equal? '() ls) #f) ((pair? ls) (let-pair ((a. d) ls) ((equal? x a) #t) ((not (equal? x a)) (member? x d))))))) (let ((b (member? a s2)) (res (list-union d s2))) ((equal? b #t) res) ((equal? b #f) `(,a.,res)))))))) We can now reorder our cond lines in whatever order we wish, and we'll still get the same behavior. And we've got something which, to my eye, looks a heck of a lot like minikanren but still runs in straight Racket. 1.2 Racket to minikanren, nally We can now start converting. We can do member? rst, and then list-union. With a blind, dogmatic obedience, I dutifully stick an `o' on the end. I do a nd-and-replace, so I never leave a call to the old version. I've been bit by that too many times to count. (define (member?o x ls) ((equal? '() ls) #f) 8

((pair? ls) (let-pair ((a. d) ls) ((equal? x a) #t) ((not (equal? x a)) (member?o x d))))))) We need to add a third argument, the out variable, change cond to conde, and we know how to handle the rst clause pretty well. (define (member?o x ls out) e ((== '() ls) (== #f out))...)) Now for that second clause, the (pair? ls) question and the let-pair are all satised by unication. It has a avor of both testing and bindingthat's why it's the giant hammer. Let's rewrite that part, and then we'll come to the inner cond next. Don't forget to freshen the variables a and d. (define (member?o x ls out) e ((== '() ls) (== #f out)) ((fresh (a d) (== `(,a.,d) ls)...)))) That cond has to be a conde, the rst clause of that cond falls pretty easily. (not (equal?... )) needs to be a disequality constraint, and the recursive call needs the third argument passed along. (define (member?o x ls out) e ((== '() ls) (== #f out)) ((fresh (a d) (== `(,a.,d) ls) e ((== x a) (== #t out)) ((=/= x a) (member?o x d out))))))) And we can go ahead and test it. 9

-> (run 1 (q) (member?o 3 '(3 4 5) q)) (#t) -> (run* (q) (member?o 3 '(3 4 5) q)) (#t) -> (run* (q) (member?o q '(3 4 5) #t)) (3 4 5) -> (run 4 (q) (member?o 3 q #t)) ((3. _.0) ((_.0 3. _.1) (=/= ((_.0 3)))) ((_.0 _.1 3. _.2) (=/= ((_.0 3)) ((_.1 3)))) ((_.0 _.1 _.2 3. _.3) (=/= ((_.0 3)) ((_.1 3)) ((_.2 3))))) -> (run 4 (q) (fresh (a b c) (== `(,a,b,c) q) (member?o a b c))) ((_.0 () #f) (_.0 (_.0. _.1) #t) ((_.0 (_.1) #f) (=/= ((_.0 _.1)))) ((_.0 (_.1 _.0. _.2) #t) (=/= ((_.0 _.1))))) Lookin' good. Let's bring it home. List-union becomes list-uniono. (define (list-uniono s1 s2 out)...) And let's go ahead and take care of everything up to the let binding. (define (list-uniono s1 s2 out) e ((== '() s1) (== s2 out)) ((fresh (a d) (== `(,a.,d) s1)...)))) The left-hand sides of the clauses of the let reveal that I'm going to need to freshen b and res. And I know that I don't need to freshen them any earlier, either. Because the order of the clauses of the let didn't matter, our transformation didn't give us information regarding which one we want to list rst. It won't impact the correctness, but it might have serious performance implications. (define (list-uniono s1 s2 out) e ((== '() s1) (== s2 out)) 10

((fresh (a d) (== `(,a.,d) s1) (fresh (b res) (member?o a s2 b) (list-uniono d s2 res)...))))) The variables on the left-hand side of the let binding are what I wanted to pass as the last elements of the relations. Cond becomes conde, we can handle the questions, and the answers similar to the manner above. Let's go ahead and tackle the rest of it. (define (list-uniono s1 s2 out) e ((== '() s1) (== s2 out)) ((fresh (a d) (== `(,a.,d) s1) (fresh (b res) (member?o a s2 b) (list-uniono d s2 res) e ((== b #t) (== res out)) ((== b #f) (== `(,a.,res) out)))))))) It's quasi-automatic, reasonably quick, and results in minikanren. -> (run 1 (q) (list-uniono '(1 2 3 4) '(5 6) q)) ((1 2 3 4 5 6)) 1.3 Back to the beginning Unfortunately, that minikanren is going to have bad divergence behavior, and in any case won't be terribly performant running backwards. But we're close. Let-binding the serious recursive relation invocations above gave us the variable names right where we needed'em, but means that they are above the calls to == in conde. Consider the three clauses of the inner fresh expression in list-uniono. If we run with s1 and s2 fresh, but with some information in the variable out, then we will continue to recur, and the information in out will not get propogated through when we do a run*. This may cause running the relation to loop innitely. We can manually reorder those clauses, and then we've got what we're after. 11

(define (member?o x ls out) e ((== '() ls) (== #f out)) ((fresh (a d) (== `(,a.,d) ls) e ((== x a) (== #t out)) ((=/= x a) (member?o x d out))))))) (define (list-uniono s1 s2 out) e ((== '() s1) (== s2 out)) ((fresh (a d) (== `(,a.,d) s1) (fresh (b res) e ((== b #t) (== res out)) ((== b #f) (== `(,a.,res) out))) (member?o a s2 b) (list-uniono d s2 res)))))) And it runs backward too. -> (run 5 (q) (fresh (a b) (== q `(,a,b)) (list-uniono a b '(1 2)))) ((() (1 2)) ((1) (1 2)) ((1 2) ()) ((1) (2)) ((1 1) (1 2))) Now we don't have a prayer of it completing with run*. We assumed our input were lists without duplicates. But we didn't ensure that. See the 5th answer above. Moreover, what we called sets are really lists with denite order. As a result running backwards might not give the expected results. If we are okay with this behavior, awesome. If not, well, then we might want to try augmenting our original functional program some, and then redoing the transformation. There's plenty to play with. 1.4 Conclusions and further guidance Though this seems like a pretty hefty program, in the Scheme of things it's not all that big. As you go on to write more minikanren, here are some things, big and small, to bear in mind: Everything has to be rst-order, defunctionalized, and using datastructure representations. 12

We let-bound the values of list-union and member?. In general, if you know what the structure of the call is going to be, use match (or pmatch) to evaluate the expression and match the pieces. This helps to avoid freshening unnecessary variables. When using match, if you're matching on a variable, and the result of the match is exactly that variable, don't create a new variable name. Conversely, if you are using match and decomposing a structure, don't shadow the variable name by rebinding it in the pattern. When you can, put multiple clauses in your let bindings. This indicates when your minikanren program has clauses for which the order doesn't matter, at least when running in the standard (functional) mode. If you plan on using the relational arithmetic suite to perform arithmetic operations, recall those are themselves recursive relations, and should be let-bound as appropriate. Decide ahead of time if you will need to tag relational numbers (to distinguish them from arbitrary lists), if so, bear the tag in mind when you are thinking about the decomposition. It can help to tag your arabic numbers before going to the minikanren arithmetic suite, in this case. When using match or pmatch, it is often advantageous to reorder your clauses in order to group similar decompositions together. When helper functions have been transformed to rst-order and data structures, often you can then in-line the data structures, to avoid freshening some variables and unications. Functions with multiple return values are perfectly acceptable. This often avoids unnecessarily constructing and destructing pairs out of a recursion. Use let-values the way we used let above, and feel free to combine let bindings into let-values bindings, if there is no dependence between them. 13