-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A first PR for a standard lib #30
base: master
Are you sure you want to change the base?
Changes from 13 commits
919235d
6896de5
9ee3f53
1c90205
d778b11
3e82b23
bf7ee54
7aad918
08262cc
ffbfb3d
edd9b56
4fffea8
0db9e06
7e2d391
2ca2dc9
a9ad888
8b6daf0
bda6942
9241d45
a262a78
771cf88
926b318
1478aa2
59925b5
7e98dfd
3249f61
6da7793
122bbc1
1374831
c4b2312
cb58bef
e7ad840
e9ff2c3
e996b11
4895eb9
2fab48c
37936e3
208f72f
88b1653
abe766d
5aafb28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
eval-main : | ||
lurkrs bool-test.lurk |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
!(:load "bool.lurk") | ||
|
||
;; test that (bool-neg nil) evaluates to t | ||
!(:assert (bool-neg nil)) | ||
!(:assert (bool-neg (bool-neg t))) | ||
|
||
;; four test cases for eq-bool | ||
!(:assert (eq-bool t t)) | ||
!(:assert (eq-bool nil nil)) | ||
!(:assert (bool-neg (eq-bool t nil))) | ||
!(:assert (bool-neg (eq-bool nil t))) | ||
|
||
;; test that (bool-neg t) evaluates to nil | ||
!(:assert (eq-bool nil (bool-neg t))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
;; some boolean operations | ||
|
||
;; negate a boolean | ||
(let ((bool-neg (lambda (x) (if x nil t)))) (current-env)) | ||
|
||
;; determine if two bools are equal | ||
(let ((eq-bool (lambda (x y) | ||
(if x y (eq y nil))))) | ||
(current-env)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
eval-main : | ||
lurkrs list-test.lurk |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
!(:load "list.lurk") | ||
|
||
;; some tests for the list module |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
;; a list is of the form (cons a (cons b (cons ... (cons nil)...))) | ||
|
||
;; list : make a list out of a (possibly unevaluated) list quote | ||
(letrec ((list (lambda (q) | ||
(if (car q) | ||
(cons (eval (car q)) (list (cdr q))) | ||
'())))) | ||
(current-env)) | ||
|
||
;; list-eq : determine if two lists are equal | ||
(letrec ((list-eq (lambda (l1 l2) | ||
(if (car l1) | ||
(if (car l2) | ||
(if (= (car l1) (car l2)) | ||
(list-eq (cdr l1) (cdr l2)) | ||
nil) | ||
nil) | ||
(if (car l2) | ||
nil | ||
t))))) | ||
(current-env)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this whole file be one big There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also what is the difference between
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha, that's funny, somehow I thought that eq didn't work on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re it being one big There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe (current-env) doesn't do what I thought. I guess it is not only returning the env at that point, it's also setting the top level's state to include all the variables from that point. In that case the name is a little misleading, the name sounds like it doesn't mutate any state. I'd call it something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Separately, when a This has the net effect that sequential forms, each extending the environment, can be used to build up an environment containing all the definitions. This trick allows us to define libraries in different files than use them, and for libraries to depend on other libraries. It also allows more modular definitions within a single file. This is mostly a hack that won't be so important once the RAM subset is available, but it certainly makes things easier in the current world, which we do want and need to support. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So @porcuquine what's your thought on whether to define the lib in one big letrec or a bunch of different ones? I lean toward just using one (assuming that would work just as well). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably multiple, but logically grouped. I'm not 100% sure, but that's my instinct. That gives us an extra layer of structure and will probably help as intermediate stage toward breaking sections out into their own file/module/whatever. It will also facilitate re-ordering for performance. |
||
|
||
;; snoc : appends to the right-hand side of a list (opposite of cons) | ||
(letrec | ||
((snoc-iter (lambda (old-list new-list) | ||
(if (car old-list) | ||
(snoc-iter (cdr old-list) (cons (car old-list) new-list)) | ||
new-list))) | ||
(snoc (lambda (x lst) (snoc-iter (snoc-iter lst '()) (cons x nil))))) | ||
(current-env)) | ||
|
||
;; length : determines the length of a list | ||
(letrec | ||
((length-iter (lambda (lst cntr) | ||
(if (car lst) | ||
(length-iter (cdr lst) (+ 1 cntr)) | ||
cntr))) | ||
(length (lambda (lst) (length-iter lst 0)))) | ||
(current-env)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can just be called Also I think we have automatic currying here, so you can try (letrec ((length_iter (lambda (cntr lst)
(if (car lst)
(length_iter (+ 1 cntr) (cdr lst))
cntr)))
(list_length (length_iter 0)))
(current-env)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you say the same goes for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
;; apply : a preliminary version of apply | ||
(letrec | ||
((apply (lambda (f list) | ||
(if (car list) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you checking to see if the car of the list is NIL? NIL shouldn't be treated specially when treated as an argument to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is intended as a check to see if the list is empty. I don't know if What you're getting at though is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's right. To your other point, NIL is the empty list, and in Lurk as in Common Lisp, NIL is the false value. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, it only makes sense to be checking to see if a list is NIL (when traversing lists). If you're checking the CAR, that's probably wrong. Even if traversing a binary tree, you probably put the NIL check at the top of the function. This is just a heuristic I'm noting since @durlicc is new to Lisp. |
||
(if (eq nil (cdr list)) | ||
(f (car list)) | ||
(apply (f (car list)) (cdr list))))))) | ||
(current-env)) | ||
dhsorens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
;; map : a list map function | ||
;; TODO : map that can take multiple lists | ||
(letrec ((map (lambda (f list) | ||
(if (eq list nil) | ||
nil | ||
(cons (f (car list)) | ||
(map f (cdr list))))))) | ||
(current-env)) | ||
|
||
|
||
;; fold : a fold function over lists | ||
(letrec | ||
((fold (lambda (lst op accum) | ||
(if (car lst) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is treating NIL specially and will terminate the computation if a NIL is encountered in LST. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I think I mentioned earlier, NIL checks on the CAR of a list when recursing are usually a sign of confused logic (not an absolute rule, but it holds in this case). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good point, I have to revisit a lot of the functions I wrote to correct this logic error 🤔 |
||
(fold (cdr lst) op (op (car lst) accum)) | ||
accum)))) | ||
(current-env)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here - suggest the (letrec
((list_fold (lambda (op accum lst)
(if (car lst)
(list_fold op (op (car lst) accum) (cdr lst))
accum)))
(sum (list_fold (lambda (x y) (+ x y)) 0)))
(sum '(1 2 3 4 5)))
=> 15 (by the way I notice There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it's how it works. All the built-in operators are hardcoded in the circuit and are not actually 'functions'. We could define functions bound to the symbols in a library in the future, as a convenience — or not. Depending on how details of future versions evolve, along with the circuit, it's possible we revisit this, but for now it's how things are by design. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good to know, I had the same question about |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
eval-main : | ||
lurkrs safe-arith-test.lurk |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
!(:load "safe-arith.lurk") | ||
|
||
;; tests for neg | ||
!(:assert-eq 0 (neg 0)) | ||
!(:assert-eq 10 (neg (- 0 10))) | ||
!(:assert-eq (- 0 10) (neg 10)) | ||
|
||
;; tests for safe-add | ||
!(:assert-eq (+ 10 (neg 43)) (safe-add 10 (neg 43))) | ||
!(:assert-eq (+ (neg 111) 321) (safe-add (neg 111) 321)) | ||
!(:assert-eq (+ 11 5443225) (safe-add 11 5443225)) | ||
!(:assert-eq (+ (neg 5443225) (neg 43)) (safe-add (neg 5443225) (neg 43))) | ||
|
||
;; tests for safe-sub | ||
|
||
|
||
|
||
;; tests for safe-mult | ||
|
||
|
||
|
||
;; tests for floor-int-div | ||
|
||
|
||
|
||
;; tests for ceil-int-div | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
;; Arithmetic operations that take into account that arithmetic in Lurk | ||
;; is done over a finite field Z mod p. | ||
;; These functions model arithmetic in Z. | ||
|
||
;; Since Lurk uses a finite field for arithmetic, arithmetic doesn't | ||
;; always function as we might expect it to | ||
;; In particular, it is possible for the product or the sum of two | ||
;; positive integers to be negative | ||
|
||
;; here we implement "safe" integer arithmetic, which is: | ||
;; integer arithmetic which evaluates correctly when it behaves like | ||
;; normal integer arithmetic, but evaluates to `nil` when it | ||
;; behaves oddly due to us using a finite field | ||
|
||
;; a simple function to switch from negative to positive and visa versa | ||
(let ((neg (lambda (n) (- 0 n)))) (current-env)) | ||
|
||
;; safe-add and safe-subtract make addition behave as in the integers | ||
(let ((safe-add (lambda (a b) | ||
(if (< a 0) | ||
(if (< b 0) | ||
;; the sum of two negatives should be negative | ||
(if (< (+ a b) 0) | ||
;; the sum of a positive and negative is safe | ||
(+ a b)) | ||
(+ a b)) | ||
(if (< b 0) | ||
;; the sum of a positive and negative is safe | ||
(+ a b) | ||
;; the sum of two positives should be positive | ||
(if (>= (+ a b) 0) | ||
(+ a b))))))) | ||
(current-env)) | ||
|
||
(let ((safe-sub (lambda (a b) | ||
(if (< a 0) | ||
(if (< b 0) | ||
;; the difference of two negatives is safe | ||
(- a b) | ||
;; the difference of `a` negative and `b` nonnegative | ||
;; should be negative | ||
(if (< (- a b) 0) | ||
(- a b))) | ||
(if (< b 0) | ||
;; the difference of `a` nonnegative and `b` negative | ||
;; should be nonnegative | ||
(if (>= (- a b) 0) | ||
(- a b)) | ||
;; the difference of `a` nonnegative and `b` nonnegative is safe | ||
(- a b)))))) | ||
(current-env)) | ||
|
||
;; safe-mult makes multiplication behave as in the integers | ||
(let ((safe-mult (lambda (a b) | ||
(if (< a 0) | ||
(if (< b 0) | ||
;; the product of two negatives is positive | ||
(if (> ( * a b) 0) | ||
( * a b)) | ||
;; the product of a negative and nonnegative is nonpositive | ||
(if (<= ( * a b) 0) | ||
( * a b))) | ||
(if (< b 0) | ||
;; the product of a nonnegative and a negative is nonpositive | ||
(if (<= ( * a b) 0) | ||
( * a b)) | ||
;; the product of two nonnegatives is nonnegative | ||
(if (>= ( * a b) 0) | ||
( * a b))))))) | ||
(current-env)) | ||
|
||
;; integer division, where division by 0 results in 0 | ||
;; first an auxiliary function `quot` | ||
(letrec | ||
((quot-iter (lambda (a b q) | ||
(if (< a b) | ||
(quot-iter b a 0) | ||
(if (< (* b (+ q 1)) 0) | ||
q | ||
(if (> (* b (+ q 1)) a) | ||
q | ||
(quot-iter a b (+ q 1))))))) | ||
(quot (lambda (a b) | ||
(if (< a 0) | ||
(quot (- 0 a) b) | ||
(if (< b 0) | ||
(quot a (- 0 b)) | ||
(quot-iter a b 0)))))) | ||
(current-env)) | ||
|
||
;; integer division that rounds down | ||
(letrec ((floor-int-div | ||
(lambda (a b) | ||
(if (= b 0) 0 | ||
(if (< a 0) (- 0 (floor-int-div (- 0 a) b)) | ||
(if (< b 0) (- 0 (floor-int-div a (- 0 b))) | ||
(if (< a b) 0 | ||
(quot a b)))))))) | ||
(current-env)) | ||
|
||
;; integer division that rounds up | ||
(letrec ((ceil-int-div | ||
(lambda (a b) | ||
(if (= b 0) 0 | ||
(if (< a 0) (- 0 (ceil-int-div (- 0 a) b)) | ||
(if (< b 0) (- 0 (ceil-int-div a (- 0 b))) | ||
(if (< a b) 1 | ||
(+ 1 (quot a b))))))))) | ||
(current-env)) | ||
|
||
|
||
;; safe integer square | ||
(let ((safe-square (lambda (x) (safe-mult x x)))) (current-env)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LIST probably requires a macro or language support to be done right (variable number of arguments).
Explicit use of EVAL is probably not desirable, but if it were, you probably want the current env to be involved.
I'm chalking this up to probably not being a useful function as written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came up with that one, and i had a newer version that uses current-env, so that you can do nested lists.
I don't see any other way to produce a literal list (that's not quoted, but rather requires eval'ing args). I mean, aside from a bunch of consing. Obviously using
eval
is a terrible hack and yeslist
is supposed to be varargs. Edit: but i think it's useful until there's a nativelist
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a clue that the concept involved is off. If we really want a function that takes a list and outputs a list, we can just use the identity function.
Normal Lisp LIST, apart from being variadic, doesn't perform explicit evaluation. It just so happens that the args are evaluated before LIST ever sees them.
For example, here is how SBCL defines LIST:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I understand this.
What I really want is lisp's LIST, but I don't think that's possible to define in lurk itself right now. I'm sure you understand why I want it, it's for the same purpose everyone uses LIST in lisp - to make a literal list with expressions in it that I want evaluated first. Without that the only way I can think to make such a list is with repeated consing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I understand. I think one urge is for Lisp's LIST, which can indeed be implemented with macros or when we have support for &REST parameters.
I think you also want something that is definitionally not LIST, but that would accomplish a practical purpose. That something is basically MAP-EVAL of some flavor — which is fine.
I just would not call it LIST, since that's confusing and also will clash with some future implementation of LIST which works as it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok yes I see your point, I started out trying to implement LIST but realized it's not possible and then kept the name on something that's not the same. What I ended up with is basically (map eval ...) like you said. I am fine with whatever it is not being in the stdlib but I will need something like this as a utility function at least until there's something better. I am ok with just copying it around. Putting it in the stdlib is probably bad because later it will have no purpose and should go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not even saying not to have such a function. I'm just saying don't call it LIST. You could call it MAP-EVAL or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, if we have
map
(which we will), writing(map eval lst)
is too trivial to be worth creating a new function.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be a little more complicated than you want to try to inline by hand. Remember:
As a test, you want to make sure something like this works:
I think for that to work, you probably need to also pass
(current-env)
todo-the-thing
.