Skip to content
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

Draft
wants to merge 41 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
919235d
preliminary modules in the std lib for bool and safe-arith
dhsorens Nov 17, 2022
6896de5
add square to safe-arith
dhsorens Nov 17, 2022
9ee3f53
add some functions for lists
dhsorens Nov 17, 2022
1c90205
add some comments on safe-arith
dhsorens Nov 17, 2022
d778b11
change _ to -
dhsorens Nov 18, 2022
3e82b23
change list-* functions to *
dhsorens Nov 18, 2022
bf7ee54
remove empty-list and change the order of the args in map
dhsorens Nov 18, 2022
7aad918
implement apply in Lurk
dhsorens Nov 18, 2022
08262cc
remove incorrect implementation of apply
dhsorens Nov 18, 2022
ffbfb3d
streamline bool and add one test
dhsorens Nov 18, 2022
edd9b56
shorten code further for bool
dhsorens Nov 18, 2022
4fffea8
correct and streamline map
dhsorens Nov 18, 2022
0db9e06
an initial, probably wrong version of apply
dhsorens Nov 18, 2022
7e2d391
add logical connectives and, or
dhsorens Nov 18, 2022
2ca2dc9
remove unecessary eq functions
dhsorens Nov 18, 2022
a9ad888
add some placeholder tests and some new functions
dhsorens Nov 18, 2022
8b6daf0
add numbered-list-n function and slight reorg
dhsorens Nov 18, 2022
bda6942
add cadr and cddr
dhsorens Nov 18, 2022
9241d45
add list reverse function
dhsorens Nov 18, 2022
a262a78
add functions for tables (lists of lists)
dhsorens Nov 18, 2022
771cf88
improve semantics of fold (thx Jeff)
dhsorens Nov 18, 2022
926b318
add concat to lists
dhsorens Nov 18, 2022
1478aa2
rename to reverse and nth, add last
dhsorens Nov 18, 2022
59925b5
Remove list
weissjeffm Nov 18, 2022
7e98dfd
Update concat to use reverse
weissjeffm Nov 18, 2022
3249f61
More car/cdr composition aliases
weissjeffm Nov 18, 2022
6da7793
Clean up apply function
weissjeffm Nov 18, 2022
122bbc1
Add functions drop and take
weissjeffm Nov 18, 2022
1374831
Add filter function
weissjeffm Nov 18, 2022
c4b2312
Use kebab-case
weissjeffm Nov 21, 2022
cb58bef
Remove call to neq, which doesn't exist
weissjeffm Nov 21, 2022
e7ad840
Handle corner case in apply1 with empty list
weissjeffm Nov 21, 2022
e9ff2c3
Fixup and add tests for list stdlib
weissjeffm Nov 21, 2022
e996b11
not equal, neq, function
dhsorens Nov 21, 2022
4895eb9
car/cdr composition fixes
weissjeffm Nov 21, 2022
2fab48c
More tests
weissjeffm Nov 21, 2022
37936e3
still incorrect table functions but slightly less incorrect
dhsorens Nov 21, 2022
208f72f
Add logical 'not'
weissjeffm Nov 21, 2022
88b1653
Use more standard and short argument names
weissjeffm Nov 22, 2022
abe766d
add some functions around trees and some around sets.
dhsorens Nov 30, 2022
5aafb28
a definition and some functions for maps. Note only numerical keys ar…
dhsorens Dec 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions std-lib/bool/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eval-main :
lurkrs bool-test.lurk
24 changes: 24 additions & 0 deletions std-lib/bool/bool-test.lurk
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
!(:load "bool.lurk")

;; test that (bool-neg nil) evaluates to t
!(:assert (bool-neg nil))
!(:assert (eq nil (bool-neg t)))

;; four test cases for eq-bool
!(:assert (eq nil (eq-bool t nil)))
!(:assert (eq nil (eq-bool nil t)))

;; test that (bool-neg t) evaluates to nil
!(:assert (eq nil (bool-neg t)))

;; test logical and
!(:assert (and t t))
!(:assert (eq nil (and t nil)))
!(:assert (eq nil (and nil t)))
!(:assert (eq nil (and nil nil)))

;; test logical or
!(:assert (or t t))
!(:assert (or t nil))
!(:assert (or nil t))
!(:assert (eq nil (or nil nil)))
13 changes: 13 additions & 0 deletions std-lib/bool/bool.lurk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; some boolean operations

;; negate a boolean
(let ((bool-neg (lambda (x) (if x nil t)))) (current-env))

;; not equal
(let ((neq (lambda (x y) (bool-neg (eq x y))))) (current-env))

;; logical and
(let ((and (lambda (b1 b2) (if b1 (if b2 t))))) (current-env))

;; logical or
(let ((or (lambda (b1 b2) (if b1 t (if b2 t))))) (current-env))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably either leave these out, or name them something else. The "real" and and or should be macros that have short-circuit logic (in or, if the first arg evals to true, it immediately returns true without evaluating the 2nd arg).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable to me 👍

2 changes: 2 additions & 0 deletions std-lib/list/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eval-main :
lurkrs list-test.lurk
66 changes: 66 additions & 0 deletions std-lib/list/list-test.lurk
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
!(:load "../safe-arith/safe-arith.lurk")
!(:load "../bool/bool.lurk")
!(:load "list.lurk")

;; test snoc
!(:assert-eq '(1 2 3 4) (snoc 4 '(1 2 3)))

;; test reverse
!(:assert-eq '(3 2 1) (reverse '(1 2 3)))
!(:assert-eq '() (reverse '()))

;; test cadr
!(:assert-eq 2 (cadr '(1 2 3)))

;; test cddr
!(:assert (= 3 (cddr '(1 2 3))))

;; test list-n
!(:assert-eq '(nil nil) (list-n 2))
!(:assert-eq 10 (length (list-n 10)))

;; test numbered-list-n
!(:assert-eq '(0 1 2 3) (numbered-list-n 3))
!(:assert-eq 11 (length (numbered-list-n 10)))

;; test nth
!(:assert-eq 4 (nth 2 '(1 2 4 3)))

;; test last
!(:assert-eq 3 (last '(1 2 3)))

;; test list-update
!(:assert-eq '(1 2 3) (list-update 1 2 '(1 1 3)))

;; test length
!(:assert-eq 3 (length '(1 2 3)))
!(:assert-eq 0 (length '()))

;; test apply1
!(:assert-eq 7 (apply1 (lambda (x y) (+ x y)) '(3 4)))
!(:assert-eq 7 (apply1 (lambda () 7) '()))

;; test map
!(:assert-eq '(2 4 6) (map (lambda (x) (* 2 x)) '(1 2 3)))
!(:assert-eq '() (map (lambda (x) (* 2 x)) '()))

;; test fold
!(:assert-eq 6 (fold (lambda (x y) (+ x y)) 0 '(1 2 3)))
!(:assert-eq 0 (fold (lambda (x y) (+ x y)) 0 '()))

;; test concat
!(:assert-eq '(1 2 3 4) (concat '(1 2) '(3 4)))
!(:assert-eq '(1 2 3 4) (concat '() '(1 2 3 4)))
!(:assert-eq '(1 2 3 4) (concat '(1 2 3 4) '()))

;; test take
!(:assert-eq '(1 2 3) (take 3 '(1 2 3 4 5)))
!(:assert-eq '() (take 0 '(1 2 3 4 5)))
!(:assert-eq '(1 2 3) (take 5 '(1 2 3)))

;; test drop
!(:assert-eq '(3 4 5) (drop 2 '(1 2 3 4 5)))
!(:assert-eq '() (drop 6 '(1 2 3 4 5)))

;; test filter
!(:assert-eq '(3 4 5) (filter (lambda (x) (> x 2)) '(1 2 3 4 0 5)))
157 changes: 157 additions & 0 deletions std-lib/list/list.lurk
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
;; !(:load "../bool/bool.lurk")

;; a list is of the form (cons a (cons b (cons ... (cons nil)...)))

;; 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))

;; reverse : reverses the order of a list
(letrec
((reverse-iter (lambda (list1 list2)
(if (car list1)
(reverse-iter (cdr list1) (cons (car list1) list2))
list2)))
(reverse (lambda (list) (reverse-iter list nil))))
(current-env))

;; cxxxr combinations (up to 3)
(let ((cadr (lambda (list) (car (cdr list))))
(caar (lambda (list) (car (car list))))
(cdar (lambda (list) (cdr (car list))))
(cddr (lambda (list) (cdr (cdr list))))
(cdddr (lambda (list) (cdr (cdr (cdr list)))))
(cddar (lambda (list) (cdr (cdr (car list)))))
(cdaar (lambda (list) (cdr (car (car list)))))
(cdadr (lambda (list) (cdr (car (cdr list)))))
(caaar (lambda (list) (car (car (car list)))))
(cadar (lambda (list) (cdr (cdr (car list)))))
(caadr (lambda (list) (car (car (cdr list)))))
(caddr (lambda (list) (car (cdr (cdr list))))))
(current-env))
Copy link
Contributor

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.

Copy link

@weissjeffm weissjeffm Nov 17, 2022

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.

         (list (lambda (q)
                 (if (car q)
                     (cons (eval (car q) (current-env))
                           (list (cdr q)))
                     nil)))

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 yes list is supposed to be varargs. Edit: but i think it's useful until there's a native list.

Copy link
Contributor

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:

(defun list (&rest args)
  "Construct and return a list containing the objects ARGS."
  args)

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.

Copy link
Contributor

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.

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.

Copy link
Contributor

@porcuquine porcuquine Nov 18, 2022

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.

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.

Copy link
Contributor

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:

  • EVAL isn't a function
  • you need the correct environment

As a test, you want to make sure something like this works:

(let ((a 1))
  (do-the-thing '(a 2 (+ a 2))))

I think for that to work, you probably need to also pass (current-env) to do-the-thing.


;; list-n : make an empty list of size n
(letrec
((list-n-iter (lambda (n list)
(if (= n 0)
list
(list-n-iter (- n 1) (cons nil list)))))
(list-n (lambda (n) (list-n-iter n nil))))
(current-env))

;; numbered-list-n : make a numbered list from 0 to n of length (n + 1)
(letrec
((numbered-list-iter (lambda (n list)
(if (= n 0)
(cons 0 list)
(numbered-list-iter (- n 1) (cons n list)))))
(numbered-list-n (lambda (n)
(numbered-list-iter n nil))))
(current-env))


;; nth : get the nth (0-indexed) value of a list
(letrec ((nth (lambda (place list)
(if (car list)
(if (= 0 place)
(car list)
(nth (- place 1) (cdr list)))))))
(current-env))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can just be called length. I think that matches common lisp. The same probably goes for most of these other functions - you usually don't need to have list in the name.

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))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you say the same goes for list-eq?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list-eq is just eq. There's no need for such a function at all.


;; last : get the last element of a list
(let ((last (lambda (list) (car (reverse list))))) (current-env))

;; list-update : update the nth (0-indexed) value of a list
;; if (< (length list) n) then list-update returns
;; an unchanged list
(letrec
((list-update-iter (lambda (place value old-list new-list)
(if (car old-list)
(if (= 0 place)
(list-update-iter
(- place 1)
nil
(cdr old-list)
(snoc value new-list))
(list-update-iter
(- place 1)
value
(cdr old-list)
(snoc (car old-list) new-list)))
new-list)))
(list-update (lambda (place value list)
(list-update-iter place value list nil))))
(current-env))
dhsorens marked this conversation as resolved.
Show resolved Hide resolved

;; length : determines the (0-indexed) length of a list
(letrec
((length-iter (lambda (lst cntr)
(if lst
(length-iter (cdr lst) (+ 1 cntr))
cntr)))
(length (lambda (lst) (length-iter lst 0))))
(current-env))

;; apply : a preliminary version of apply
(letrec
((apply1 (lambda (f list)
(if list
(if (cdr list)
(apply1 (f (car list)) (cdr list))
(f (car list)))
(f)))))
(current-env))

;; 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))

;; filter : takes a predicate and a list and returns only the items
;; that match the predicate
(letrec ((filter (lambda (pred lst)
(if lst
(if (pred (car lst))
(cons (car lst) (filter pred (cdr lst)))
(filter pred (cdr lst)))))))
(current-env))

;; fold : a fold function over lists
(letrec
((fold (lambda (op accum lst)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For stylistic, consistency, let's use ACC for accumulator variable. I'd personally use L for lists also, but I feel less strongly about that. LST doesn't feel very Lispy. LIST would be better, but more verbose (hence my preference for L).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like list because it collides with the function list. Although that brings up the question of whether lurk is a lisp-1 or lisp-2. let's see:

> (let ((a (lambda () 2))
	(a 1))
    (a))
[6 iterations] => ERROR!

lisp-1. So naming collision is a problem for LIST. I agree with L then.

(if (car lst)
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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).

Copy link
Author

Choose a reason for hiding this comment

The 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 op (op accum (car lst)) (cdr lst))
accum))))
(current-env))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside, I'm providing an example which implements this slightly differently, in a way that is more efficient for Lurk. We don't necessarily need to use this, but it may be instructive. I'll provide output showing the difference in iteration count so you see the point.

> (let ((fold (lambda (op acc l)
              (letrec ((f (lambda (acc l)
                            (if l
                                (f (op acc (car l)) (cdr l))
                                acc))))
                (f acc l)))))
  (fold (lambda (a b) (+ a b))
        0
        '(1 2 3 4 5 6 7 8 9 10)))
[415 iterations] => 55

Compare with the cleaned-up version of the above code:

[225 iterations] => 15
> (letrec ((fold (lambda (op acc l)
                 (if l
                     (fold op (op acc (car l)) (cdr l))
                     acc))))
  (fold (lambda (a b) (+ a b))
        0
        '(1 2 3 4 5 6 7 8 9 10)))
[460 iterations] => 55


;; concat : concatenates two lists
(let ((concat (lambda (a b)
(fold (lambda (l i) (cons i l))
b
(reverse a)))))
(current-env))

;; drop : drops n items from the front of list l, returning the rest
(letrec ((drop (lambda (n l)
(if (= n 0)
l
(if l
(drop (- n 1) (cdr l)))))))
(current-env))

;; take : takes the first n items from the front of the list l
(letrec ((take (lambda (n l)
(if (= n 0)
nil
(if l
(cons (car l) (take (- n 1) (cdr l))))))))
(current-env))
2 changes: 2 additions & 0 deletions std-lib/safe-arith/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eval-main :
lurkrs safe-arith-test.lurk
28 changes: 28 additions & 0 deletions std-lib/safe-arith/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


Loading