R-LINQ: .NET’s LINQ in Racket
source code: https://github.com/trajafri/r-linq
This package provides a simple implementation of .NET’s LINQ in Racket.
1 Query Syntax
LINQ’s grammar (as described here) is represented in the Racket language as follows:
query-expression | = |
| |||||
query-body-clause | = | (from var in src) | |||||
| | (join var in src on expr comp-func expr) | ||||||
| | (join var in src on expr comp-func expr into var) | ||||||
| | (let var expr) | ||||||
| | (where expr) | ||||||
| | (orderby [expr comp-func] ...+) | ||||||
| | (select expr into var) | ||||||
| | (group expr by expr into var) | ||||||
final-query-clause | = | (select expr) | |||||
| | (group expr by expr) |
A var is a sequence of characters that can be used as a variable in the Racket language.
A src is an expression that evaluates to a sequence.
A expr is a Racket expression.
A comp-func is a Racket procedure that can compare two values.
2 Standard LINQ Query Operators
Following operators can be used in a LINQ query body (a query body must terminate with final query operator). The documentation below is based on the official documentation:
final query operator
(select expr)
> (from i in '(1 2 3) (select i)) '(1 2 3)
> (from i in '(1 2 3) (select (+ i 3))) '(4 5 6)
> (from i in (list (λ (x) (+ x 3)) (λ (x) (+ x 4)) (λ (x) (+ x 5))) (select (i 1))) '(4 5 6)
Since a select operator finalizes a query, it might be helpful to post-process the results of a select clause. To do this, the into form of select can be used:
> (from i in '(1 2 3) (select i into j) (select j)) '(1 2 3)
> (from i in '(1 2 3) (select (+ i 3) into j) (select (- j 3))) '(1 2 3)
More usage of select can be seen under other query operator’s examples.
> (from i in '("hello" "there" "hi" "cow" "car" "cat" "dog" "test") (group i by (substring i 0 1)))
'(("h" ("hello" "hi"))
("t" ("there" "test"))
("c" ("cow" "car" "cat"))
("d" ("dog")))
> (from i in '(1 2 3 4 5 6 7 8 9) (group i by (modulo i 3))) '((1 (1 4 7)) (2 (2 5 8)) (0 (3 6 9)))
> (from i in '(1 2 3 4 5 6 7 8 9) (group i by (even? i))) '((#f (1 3 5 7 9)) (#t (2 4 6 8)))
> (from i in '(1 2 3 4 5 6 7 8 9) (group (+ i 1) by (even? i))) '((#f (2 4 6 8 10)) (#t (3 5 7 9)))
> (from friendship in `((Turab Paulette) (Turab Joshua) (Paulette Turab) (Lalo Turab) (Dana Joe) (Joshua Lalo) (Jacob Nick) (Nick Lalo) (Sam Joshua) (Will Jacob) (Brie Dana) (Joe Joshua) (Joe Will)) (group (cadr friendship) by (car friendship)))
'((Turab (Paulette Joshua))
(Paulette (Turab))
(Lalo (Turab))
(Dana (Joe))
(Joshua (Lalo))
(Jacob (Nick))
(Nick (Lalo))
(Sam (Joshua))
(Will (Jacob))
(Brie (Dana))
(Joe (Joshua Will)))
Similar to select, we can post-process the results of a group clause by using an into form:
> (from i in '("hello" "there" "hi" "cow" "car" "cat" "dog" "test") (group i by (substring i 0 1) into groups) (select (cdr groups) into res) (select (car res))) '(("hello" "hi") ("there" "test") ("cow" "car" "cat") ("dog"))
> (from i in '(1 2 3 4 5 6 7 8 9) (group i by (even? i) into groups) (select (cadr groups))) '((1 3 5 7 9) (2 4 6 8))
> (from i in '("hello" "there" "hi" "cow" "car" "cat" "dog" "test") (let k (substring i 0 1)) (group i by k))
'(("h" ("hello" "hi"))
("t" ("there" "test"))
("c" ("cow" "car" "cat"))
("d" ("dog")))
> (from i in '(1 2 3) (let j (+ i 1)) (select j)) '(2 3 4)
> (from i in '(1 2 3) (let k add1) (select (k i))) '(2 3 4)
> (from i in '(1 2 3) (let k (> i 1)) (select k)) '(#f #t #t)
> (from i in '(1 2 3 4 5 6 7 8 9) (let k (odd? i)) (group i by k)) '((#t (1 3 5 7 9)) (#f (2 4 6 8)))
query operator
(where expr)
The data source on which the query or sub-query will be run.
A local range variable that represents each element in the source sequence.
After initializing a query, the mentioned form of from can be used anywhere in a query body clause to introduce a new data source. The data source referenced in the from clause must be sequence.
> (from i in '(1 2 3) (from j in '(4 5 6)) (select (list i j))) '((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6))
> (from i in '(1 2 3) (from j in '(4 5 6)) (from k in '(1 1 1)) (select k)) '(1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)
> (from i in (list 1 2 3) (from j in (list 3 4 5)) (where (= (+ i j) 5)) (select (list i j))) '((1 4) (2 3))
> (from i in (list 1 2 3) (where (> i 1)) (from j in '(4 5 6)) (where (> j 5)) (select (list i j))) '((2 6) (3 6))
> (letrec [(! (λ (x) (if (zero? x) 1 (* (! (sub1 x)) x))))] (from i in (list 1 2 3) (let fact (! i)) (from j in '(4 5 6)) (where (> j 4)) (select (list fact j)))) '((1 5) (1 6) (2 5) (2 6) (6 5) (6 6))
A join clause takes two source sequences as input. The elements in each sequence must contain a property that can be compared using the function passed in the join clause. The expr on the right can not contain any range variable other than the one being introduced. The expr on the left can contain any range variable other than the one being introduced.
> (from i in '(1 2 3) (join j in '(4 5 6) on 1 = 1) (select (list i j))) '((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6))
> (from i in '(1 2 3) (from j in '(4 5 6)) (join k in '(1 1 1) on i > (+ 1 k)) (select (list k j))) '((1 4) (1 4) (1 4) (1 5) (1 5) (1 5) (1 6) (1 6) (1 6))
> (from fship in `((Turab Paulette) (Turab Joshua) (Paulette Turab) (Lalo Turab) (Dana Joe) (Joshua Lalo) (Jacob Nick) (Nick Lalo) (Sam Joshua) (Will Jacob) (Brie Dana) (Joe Joshua) (Joe Will)) (join j in '(Turab Paulette Joshua Lalo) on (car fship) equal? j) (select (list j (cadr fship)))) '((Turab Paulette) (Turab Joshua) (Paulette Turab) (Lalo Turab) (Joshua Lalo))
> (from person in (list (cons "Peter" (cons 18 #t)) (cons "Jack" (cons 12 #f)) (cons "Tom" (cons 29 #t)) (cons "Pat" (cons 19 #t))) (join comp-owner in (list (cons "Sam" "Mac") (cons "Tom" "Dell") (cons "Jack" "Lenovo") (cons "Peter" "Toshiba") (cons "Tom" "Mac")) on (car person) string=? (car comp-owner)) (select (list (car person) (cddr person) (cdr comp-owner))))
'(("Peter" #t "Toshiba")
("Jack" #f "Lenovo")
("Tom" #t "Dell")
("Tom" #t "Mac"))
To perform a group-join, you can use the into form of join:
> (from person in (list (cons "Peter" (cons 18 #t)) (cons "Jack" (cons 12 #f)) (cons "Tom" (cons 29 #t)) (cons "Pat" (cons 19 #t))) (join comp-owner in (list (cons "Sam" "Mac") (cons "Tom" "Dell") (cons "Jack" "Lenovo") (cons "Peter" "Toshiba") (cons "Tom" "Mac")) on (car person) string=? (car comp-owner) into groups) (select (list (car person) (cddr person) (map cdr groups))))
'(("Peter" #t ("Toshiba"))
("Jack" #f ("Lenovo"))
("Tom" #t ("Dell" "Mac"))
("Pat" #t ()))
query operator
(orderby [key-expr comp-func] ...+)
> (from i in '(1 2 3) (orderby [i >]) (select i)) '(3 2 1)
> (from i in '(1 2 3) (from j in '(1 2 1 2)) (orderby [i >] [j >]) (select (list i j))) '((3 2) (3 2) (3 1) (3 1) (2 2) (2 2) (2 1) (2 1) (1 2) (1 2) (1 1) (1 1))
> (from i in '(1 2 3) (from j in '(1 2 1 2)) (orderby [i >] [j <]) (select (list i j))) '((3 1) (3 1) (3 2) (3 2) (2 1) (2 1) (2 2) (2 2) (1 1) (1 1) (1 2) (1 2))
3 Query Functions
The operators above are implemented using the following functions that are also provided by r-linq. The documentation below is based on the official documentation:
procedure
(select sel-func src) → (listof Y)
sel-func : (X -> Y) src : (sequenceof X)
> (select (λ (x) (+ x 2)) '(1 2 3)) '(3 4 5)
> (select add1 4) '(1 2 3 4)
> (select (λ (x) x) "hello") '(#\h #\e #\l #\l #\o)
procedure
(select-many sel-func res-func src) → (listof Z)
sel-func : (X -> Y) res-func : (X Y -> Z) src : (sequenceof X)
> (select-many (λ (x) '(4 5 6)) list '(1 2 3)) '((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6))
> (select-many add1 list 3) '((0 0) (1 0) (1 1) (2 0) (2 1) (2 2))
> (select-many list (λ (x y) (map + x y)) '((1 2 3) (4 5 6) (7 8 9))) '((2 4 6) (8 10 12) (14 16 18))
procedure
(where pred src) → (listof X)
pred : (X -> boolean) src : (sequenceof X)
> (where even? '(1 2 3 4 5)) '(2 4)
> (where (compose even? char->integer) "hello") '(#\h #\l #\l)
> (where (λ (x) (> (length x) 3)) '(() (1 2) (3 2 4 3 1) (1 3 2 4) (12 3 1))) '((3 2 4 3 1) (1 3 2 4))
procedure
(groupby sel-func part-by-func src)
→ (listof (list Z (listof Y))) sel-func : (X -> Y) part-by-func : (X -> Z) src : (sequenceof X)
> (groupby (λ (x) x) (λ (x) (modulo x 3)) '(1 2 3 4 5 6)) '((1 (1 4)) (2 (2 5)) (0 (3 6)))
> (groupby add1 even? '(1 2 3 4 5 6)) '((#f (2 4 6)) (#t (3 5 7)))
procedure
(join outer-sel-func inner-sel-func sel-when sel-func outer-src inner-src) → (listof Z) outer-sel-func : (A -> X) inner-sel-func : (B -> Y) sel-when : (X Y -> boolean) sel-func : (A B -> Z) outer-src : (sequenceof A) inner-src : (sequenceof B)
> (join (λ (x) x) (λ (x) x) (λ (x y) (= 0 (modulo x y))) list '(1 2 3 4 5) '(1 2 3 4 5)) '((1 1) (2 1) (2 2) (3 1) (3 3) (4 1) (4 2) (4 4) (5 1) (5 5))
> (join (λ (x) x) (λ (x) x) equal? (λ (x y) (cons x y)) "helo" "helo") '((#\h . #\h) (#\e . #\e) (#\l . #\l) (#\o . #\o))
procedure
(group-join outer-sel-func inner-sel-func sel-when sel-func outer-src inner-src) → (listof Z) outer-sel-func : (A -> X) inner-sel-func : (B -> Y) sel-when : (X Y -> boolean) sel-func : (A (listof B) -> Z) outer-src : (sequenceof A) inner-src : (sequenceof B)
> (group-join (λ (x) x) (λ (x) x) (λ (x y) (= 0 (modulo x y))) (λ (x y) (list x y)) '(1 2 3 4 5) '(1 2 3 4 5)) '((1 (1)) (2 (1 2)) (3 (1 3)) (4 (1 2 4)) (5 (1 5)))
procedure
(orderby src sort-seq) → (listof X)
src : (sequenceof X) sort-seq : (nelistof [X X -> boolean])
> (orderby '((1 . 2) (2 . 3) (3 . 3) (1 . 1) (2 . 2)) (list (λ (x y) (> (cdr x) (cdr y))) (λ (x y) (< (car x) (car y))))) '((2 . 3) (3 . 3) (1 . 2) (2 . 2) (1 . 1))
> (orderby '((1 . 2) (2 . 3) (3 . 3) (1 . 1) (2 . 2)) (list (λ (x y) (> (car x) (car y))) (λ (x y) (< (cdr x) (cdr y))))) '((3 . 3) (2 . 2) (2 . 3) (1 . 1) (1 . 2))
4 Literals
These are the literals used in linq query operators.
syntax
syntax
syntax
syntax