Coder Social home page Coder Social logo

schemedoc / cookbook Goto Github PK

View Code? Open in Web Editor NEW
29.0 5.0 3.0 758 KB

New Scheme Cookbook

Home Page: https://cookbook.scheme.org

JavaScript 22.82% Shell 1.69% Scheme 71.67% CSS 3.82%
scheme-programming-language scheme r7rs r7rs-scheme r5rs r5rs-scheme scheme-cookbook scheme-language

cookbook's People

Contributors

arthurgleckler avatar dependabot[bot] avatar jcubic avatar lassik avatar wasamasa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

cookbook's Issues

Sorry, I didn't follow the process.

Sorry, I didn't follow the process. I accidentally pushed my index-list contribution directly to the repo. Please let me know if there are any fixes I should make.

[Recipe] Finding the most frequent element in List

Problem

You have a list and you need to find the element with duplicates and find the most frequent element. If the list has no duplicates is should return #f.

Solution

(define (most-frequent xs)
  (let count-all-elements ((xs xs) (counters '()))
    (if (null? xs)
        (let find-max-count ((counters counters) (best #f))
          (if (null? counters)
              (and best (car best))
              (find-max-count
               (cdr counters)
               (let* ((counter (car counters))
                      (count (cdr counter)))
                 (if (and (> count 1) (or (not best) (> count (cdr best))))
                     counter
                     best)))))
        (count-all-elements
         (cdr xs)
         (let* ((x (car xs))
                (counter (assoc x counters)))
           (if (not counter)
               (cons (cons x 1) counters)
               (begin (set-cdr! counter (+ (cdr counter) 1))
                      counters)))))))

SRFI

using hash tables (SRFI 125)

(define (most-frequent xs)
  (define (inc n) (+ n 1))
  (let ((counts (make-hash-table equal?)))
    (let loop ((xs xs) (best-x #f))
      (if (null? xs)
          best-x
          (loop (cdr xs)
                (let ((x (car xs)))
                  (hash-table-update!/default counts x inc 0)
                  (let ((count (hash-table-ref counts x)))
                    (if (and (> count 1)
                             (or (not best-x)
                                 (> count (hash-table-ref counts best-x))))
                        x
                        best-x))))))))

Credit @lassik

Usage

(most-frequest '(1 2 3 4 2 3 4 2 2 2))
;; ==> 2
(most-frequest '(1 2 3 4 5 6 7))
;; ==> #f

Memory leak in Arthur Emacs code

This is the code Arthur Emacs symbol mapping

(defvar pretty-scheme-keywords
  (mapcar (lambda (pair)
            (cons (concat "\\(" (regexp-quote (car pair)) "\\)")
                  (cdr pair)))
          '(("->"  . #x2192)
            ("<="  . #x2264)
            ("<==" . #x21D0)
            (">="  . #x2265)
            ("==>" . #x21D2)))
  "Alist from regexps to Unicode code points.")

(defun prettify-scheme ()
  (add-to-list 'prettify-symbols-alist '("lambda" . #x3BB))
  (font-lock-add-keywords
   nil
   (mapcar (lambda (keyword)
             `(,(car keyword)
               (0 (progn (compose-region (match-beginning 1) (match-end 1)
                                         ,(cdr keyword)
                                         'decompose-region)
                         nil))))
           pretty-scheme-keywords))
  (turn-on-font-lock))

(add-hook 'scheme-mode-hook 'prettify-scheme)

It seems that on each opening of Scheme file new lambda will be added to the list which makes the list grow and grow on each open file.

I think that this line:

(add-to-list 'prettify-symbols-alist '("lambda" . #x3BB))

Should be outside of the function is it's called only once.

syntax-symbol?

@lassik What do you think about this macro syntax-symbol? it's based on Oleg Kiselyov. I've found it while searching for missing macros from SRFI-46.

I think that if multiple people contributed to the final form of this macro, we can consider it Public Domain. We can credit Oleg. What you think?

Is there anyway for us to look up symbols of one module like Python dir(object) to look up the attributes (methods and attributes)

Is there anyway for us to look up symbols in REPL in one module(,m r5rs) ,and like Python dir(object) to look up the attributes (methods and attributes)? Seem like there is an egg apropos to help out. But as I installed it. It is more like emacs-symbolist to look up symbols present in the currently running REPL session. For instance, call-with-port is in the module r7rs in chicken implementation, But after import r7rs, whichever module (,m r5rs or ,m r7rs)are you in, they always check with symbols by ,a call-with-port in the module r7rs .

Improve get-type-of

The Cookbook should (as it says) emphasize modern solutions. I wouldn't call the solution

(define (type-of obj)

modern because it loops through a list effectively built at runtime, preventing compiler optimizations. A "modern" solution would use modern macros. :) Or a hardcoded cond expression where the compiler can inline the primitives.

An independent question: What is a compelling use case for the procedure?

[Recipe] Convert any value to string

Problem

You need a function that will convert any value to a string

Solution

(define-values (displayed written)
  (let ((repr (lambda (fn)
                (lambda (object)
                  (call-with-port (open-output-string)
                                  (lambda (port)
                                    (fn object port)
                                    (get-output-string port)))))))
    (values (repr display) (repr write))))

Credit @jcubic

SRFI

The functions displayed and written are also defined by SRFI 166.

Usage

(define (print x)
  (display x)
  (newline))

(print (written #\x))
;; ==> #\x
(print (written "foo"))
;; ==> "foo"
(print (written '(1 2 3)))
;; ==> (1 2 3)

Document how to build output html

There are few scheme scripts in repo, but it's not clear which function run and in which order.

We can use make (create Makefile) and put all stuff that needs to be run to build all the HTML files.

[Recipe] Creating subset of an AList

Problem

You have Alist and you need to return the new Alist only with specified keys.

Solution

(define (match value)
  (lambda (x)
    (equal? x value)))

(define (alist->subset keys alist)
  (let iter ((alist alist) (keys keys) (result '()))
    (if (or (null? alist) (null? keys))
        (reverse result)
        (let* ((pair (car alist)) (key (car pair)))
          (if (member key keys)
              (let ((keys (remove (match keys) keys)))
                 (iter (cdr alist) keys (cons pair result)))
              (iter (cdr alist) keys result))))))

Credit @jcubic

It uses the remove function from Recipe #6

The code assumes that Alist has all unique keys.

Usage

(define alist '((foo . 10) (bar . 20) (baz . 30) (quux . 40)))
(alist->subset '(quux foo) alist)
;; => '((foo . 10) (quux . 40))

Accidentally quadratic

The proposed solution for string-split uses string-ref and substring. Both are allowed to be O(n) where n is the length of the string. Thus the solution can be accidentally quadratic on some Schemes. It should not be promoted but rewritten.

(if (not (char-delimiter? (string-ref string b)))

Can it be looked up in the local site by downloading the whole schemecookbook.org.tgz

As in my site, it is needed a proxy to access the host schemecookbook.org. That means I can not see this cookbook at all. But the cookbook look likes so amazing to have.
So I downloaded the file schemecookbook.org.tgz from in you site. Upzip to it the files in my laptop. Then go the folder ..\schemecookbook.org\Cookbook , edit the fileindex.htmlin Emacs.

  1. Find the tag <div contentbox> , and put corsal position there.
  2. set-mark_command, Forward Tag in html-mode.
  3. copy-paste the content to the temp buffer.
  4. see the temp buffer by the command browse-url-of-buffer

And I got the item list of the cookbook in the webpage. Then by the item name in this page I can find which one is what I am interesting, then come to the corresponding folder in the unzipped folder schemecookbook.org for the webpage(index.html) of each item. Of course edit index.html in Emacs as the same way I did in the above.

For the search ability, like grep the title or code in the target folder schemecookbook.org.
The question is that by the above way to reference the cookbook we have out there. Is it missing a lots?

[Recipe] Find matching files in directory tree

Problem

You have a directory containing an unknown set of subdirectories and files. For example:

a/a.png
a/a.html
b/b.html
b/c/c.html
c/d.png
d/

Return a list with the relative pathname (e.g. "b/c/c.html") of each .html file in this tree.

  • Do not include file names starting with a dot ("dotfiles").
  • Do not visit subdirectories whose names start with a dot.
  • The list does not have to be sorted.

No portable solution exists at the moment. SRFI 170 or implementation-specific libraries can be used.

Solution

SRFI

Using SRFI 170 (and string-suffix-ci? comes from SRFI 13)

(define (directory-tree-fold merge state root)
  (define (path-append a b)
    (if (and a b) (string-append a "/" b) (or a b)))
  (let recurse ((state state) (relative #f))
    (let* ((path (path-append root relative))
           (state (merge path state))
           (directory? (file-info-directory? (file-info path #t))))
      (if (not directory?) state
          (let loop ((state state) (names (directory-files path)))
            (if (null? names) state
                (loop (recurse state (path-append relative (car names)))
                      (cdr names))))))))

(directory-tree-fold
 (lambda (name names)
   (if (string-suffix-ci? ".html" name) (cons name names) names))
 '() ".")

Credit @lassik

Do we have a need to standardize the options or part of the options for the top level script to run?

As learning to run the top level script in the different implementation of Scheme, sound like there are different options available for me. But it takes a while for me to figure out what they are, how can it be used when running the script. Sound like there is a need to standardize the options or part of them for the top level script to run. But is it possible for the implementors point view to do that? Or we already did it before?

[Recipe] Split list into groups of N elements

Problem

Write a procedure (group n lst) that splits a list into groups of n consecutive elements.

If the list length is not divisible by n, the last group will have fewer than n elements.

The procedure should return a list of the groups in order, such that each group is a sublist of the main list.

Solution

(define (group n lst)
  (if (< n 1)
      (error "group: n must be positive" n)
      (let loop ((lst lst) (m n) (g '()) (gs '()))
        (cond ((and (null? lst) (null? g))
               (reverse gs))
              ((or (null? lst) (zero? m))
               (loop lst n '() (cons (reverse g) gs)))
              (else
               (loop (cdr lst) (- m 1) (cons (car lst) g) gs))))))

Credit: @lassik

Usage

;;; Normal examples:

(group 1 (iota 10))
;; ==> ((0) (1) (2) (3) (4) (5) (6) (7) (8) (9))

(group 2 (iota 10))
;; ==> ((0 1) (2 3) (4 5) (6 7) (8 9))

(group 3 (iota 10))
;; ==> ((0 1 2) (3 4 5) (6 7 8) (9))

(group 4 (iota 10))
;; ==> ((0 1 2 3) (4 5 6 7) (8 9))

(group 5 (iota 10))
;; ==> ((0 1 2 3 4) (5 6 7 8 9))

(group 6 (iota 10))
;; ==> ((0 1 2 3 4 5) (6 7 8 9))

;;; Special cases:

(group 20 (iota 10))
;; ==> ((0 1 2 3 4 5 6 7 8 9))

(group 1 '())
;; ==> ()

(group 2 '())
;; ==> ()

New Cookbook / Licensing issues

Because the old cookbook has an LGPL license we can't use it for a new cookbook and force people to include a license.

Solution: create a completely new cookbook.
I propose license CC-BY-SA same as StackOverflow from where we can grab some solutions to common problems.

If you want to share the recipe please create an issue.

[Recipe] Removing duplicates from a List

Problem

You have a list and you want to remove any duplicated elements from the list and the order should not change.
The solution can use the equal? function or use the comparison function as an argument.

Solution

Using SRFI

Using hash tables (SRFI 125)

(define (remove-duplicates xs)
  (let ((seen (make-hash-table equal?)))
    (let loop ((xs xs) (new-list '()))
      (if (null? xs)
          (reverse new-list)
          (loop (cdr xs)
                (let ((x (car xs)))
                  (if (hash-table-contains? seen x)
                      new-list
                      (begin (hash-table-set! seen x #t)
                             (cons x new-list)))))))))

Credit @lassik

Usage

(remove-duplicates '(1 2 3 1 2 4 4 5 6 7 5))
;; ==> '(1 2 3 4 5 6 7)

pattern matching

There is SRFI-200 (Draft) for pattern matching but I have my own implementation, that I've created as an example for my Scheme implementation:

Here is the whole code, we can use this in cookbook, the code is not that long, but unfortunately, it uses lisp-macros I'm not sure if you can rewrite it with syntax-rules (or syntax-case)

;; Example pattern matching macro
;;
;; This file is part of the LIPS - Scheme implementation in JavaScript
;; Copyriht (C) 2019-2021 Jakub T. Jankiewicz <https://jcubic.pl/me>
;; Released under MIT license

(cond-expand
 (lips)
 (guile
  (define (object? x) #f)
  (define (type x)
          (cond ((string? x) "string")
                ((pair? x) "pair")
                ((null? x) "nil")
                ((number? x) "number")
                ((vector? x) "array")
                ((procedure? x) "function")
                ((char? x) "character")))))

;; ---------------------------------------------------------------------------------------
(define (compare a b)
  "(compare a b)

   Function that compare two values. it compare lists and any element of the list
   can be a function that will be called with other value. e.g.:
   (compare (list (list 'a) 'b) (list pair? 'b))"
  (cond ((and (pair? a) (pair? b))
         (and (compare (car a) (car b))
              (compare (cdr a) (cdr b))))
        ((and (vector? a) (vector? b))
         (compare (vector->list a) (vector->list b)))
        ((and (object? a) (object? b))
         (compare (vector->list (--> Object (keys a)))
                  (vector->list (--> Object (keys b)))))
        ((string=? (type a) (type b)) (eq? a b))
        ((and (procedure? a) (not (procedure? b)))
         (a b))
        ((and (not (procedure? a)) (procedure? b))
         (b a))
        (else #f)))

;; ---------------------------------------------------------------------------------------
(define-macro (auto-quote arg)
  "(auto-quote list)

   Macro that create list recursively but take symbols from scope"
  (if (pair? arg)
      `(list ,@(map (lambda (item)
                      (if (symbol? item)
                          item
                          (if (pair? item)
                              `(auto-quote ,item)
                              `,item)))
                    arg))
      arg))

;; ---------------------------------------------------------------------------------------
(define-macro (match-pattern expr . list)
  "(match-pattern ((pattern . body) ...))

   Pattern matching macro. examples:
   (match-pattern (1 (pair? pair?) 2) ((1 ((1) (1)) 2) (display \"match\")))
   ;; match
   (match-pattern (1 (pair? pair?) 2) ((1 ((1)) 2) (display \"match\")))
   (match-pattern (1 (pair? pair?) 2) ((1 ((1)) 2) (display \"match\")) (true \"rest\"))
   ;; rest"
 (if (pair? list)
     (let ((ex-name (gensym)))
       `(let ((,ex-name (auto-quote ,expr)))
          (cond ,@(map (lambda (item)
                         (if (eq? (car item) #t)
                             `(else ,(cadr item))
                             `((compare ,ex-name (auto-quote ,(car item))) ,@(cdr item))))
                       list))))))

;; ---------------------------------------------------------------------------------------
(match-pattern (1 (pair? pair?) 2) ((1 ((1) (1)) 2)
                                    (display "match")
                                    (newline)))

The license is MIT but I'm fine in releasing it for scheme cookbook in multiple licenses.

@lassik what do you think? Can we put lisp macros into cookbook or should we do only pure R7RS compatible code?

[Recipe] Removing item from a list

Problem

You have a list and want to remove items that return true for a given predicate function

Solution

(define (remove fn list)
  (let iter ((list list) (result '()))
    (if (null? list)
        (reverse result)
        (let ((item (car list)))
          (if (fn item)
              (iter (cdr list) result)
              (iter (cdr list) (cons item result)))))))

Credit @jcubic

SRFI

This function is part of SRFI-1, which also has filter that is reverse of remove, where if the function returns true the item will be kept in the output list.

Usage

(define >10 (lambda (x) (> x 10)))
(remove >10 '(1 2 3 4 10 11 12 13 14))
;; ==> (1 2 3 4 10)

Macro similar to letrec but for macros

This is the first recipe that I've created using lisp macro.

The problem is that we want syntax similar to letrec but for macros (not that the same can be rewritten as a hygienic macro):

(define-macro (let-macro macros . body)
  `(let ()
     ,@(map (lambda (macro)
              (if (and (symbol? (car macro))
                       (symbol=? (caadr macro) 'lambda))
                  (let ((name (car macro))
                        (args (cadadr macro))
                        (body (cddadr macro)))
                    `(define-macro (,name ,@args)
                       ,@body))
                   (error "let-macro: invalid syntax")))
            macros)
     (begin
       ,@body)))

(define (print . args)
  (for-each (lambda (arg)
              (display arg)
              (newline))
            args)

(let-macro ((foo (lambda (x) `'(,x ,x)))
            (bar (lambda (x y . rest) `(list ,x ,y ,@rest))))
   (print (list (foo a) (foo b) (bar 1 2 3 4))))

[Recipe] Searching sub string inside a string

Problem

You want to search and find the index of a string inside a bigger string.

Solution

(define (string-find haystack needle . rest)
  (let ((start (if (null? rest) 0 (car rest))))
    (let* ((haystack-len (string-length haystack))
           (needle-len (string-length needle))
           (start 0))
      (let loop ((h-index start)
                 (n-index 0))
        (let ((h-char (string-ref haystack h-index))
              (n-char (string-ref needle n-index)))
          (if (char=? h-char n-char)
              (if (= (+ n-index 1) needle-len)
                  (+ (- h-index needle-len) 1)
                  (loop (+ h-index 1) (+ n-index 1)))
              (if (= (+ h-index 1) haystack-len)
                  #f
                  (loop (+ h-index 1) 0))))))))

SRFI

The same functionality is provided by SRFI-13 function string-contains and string-contains-ci

Credit: @jcubic

Usage

(let* ((input "This is hello world")
       (search "hello")
       (found (string-find input search)))
  (if found
      (begin
        (display (substring input found))
        (newline))))
;; ==> "helo world"

Add license

We need to decide what license the cookbook will have. Then we will need to add to the website footer (best on each recipe page) and into README and add a file with a license.

[Recipe] Find the index of an element in a list

Problem

You have a list and a function and you want to find the first index for an item for which the function returns true.

Solution:

(define (list-index fn list)
  (let iter ((list list) (index 0))
    (if (null? list)
        -1
        (let ((item (car list)))
          (if (fn item)
              index
              (iter (cdr list) (+ index 1)))))))

Credit @jcubic

SRFI

This function is defined in SRFI-1

Usage

(define >10 (lambda (x) (> x 10)))
(index >10 '(1 2 3 4 10 11 12 13 14))
;; ==> 5

Add CONTRIBUTING.md file

This is file is a standard file used on GitHub when creating a PR and we can link it in README.

The file should say how to create recipes. We will need to decide how to go about it. If we migrate to PR, then we will need to remove or improve the recipe issue template and create a template for PR linked in CONTRIBUTING file. It actually can be the same file since it's already in markdown.

Also, it would be nice to automate publishing the cookbook using CI/CD, we can use free Travis service or GitHub actions.

[Recipe] Split a string

Problem

Split a string into substrings at known delimiters.

Solution

(define (string-split char-delimiter? string)
  (define (maybe-add a b parts)
    (if (= a b) parts (cons (substring string a b) parts)))
  (let ((n (string-length string)))
    (let loop ((a 0) (b 0) (parts '()))
      (if (< b n)
          (if (not (char-delimiter? (string-ref string b)))
              (loop a (+ b 1) parts)
              (loop (+ b 1) (+ b 1) (maybe-add a b parts)))
          (reverse (maybe-add a b parts))))))

Credit @lassik

Usage

(string-split char-whitespace? "")
;; ==> ()

(string-split char-whitespace? " \t  ")
;; ==> ()

(string-split char-whitespace? "ab")
;; ==> ("ab")

(string-split char-whitespace? "ab   c  d \t efg ")
;; ==> ("ab" "c" "d" "efg")

[Recipe] Select random elements from a list minimising unwanted duplicates

Problem

I want to be able to take random elements from a list without getting unwanted duplicates (so if I ask for 4 random elements from the list '(a b c d) I don't want to get to '(d a ba ), for example).

Solution

(define (pick elements how-many)
  (if (< how-many 0)
      (error "Cannot create a list of negative length."
             how-many)
      (letrec* ((list-length (length elements))
                (new-index (lambda (iteration-list-length)
                             (random-integer (if (zero? iteration-list-length)
                                                 list-length
                                                 iteration-list-length))))
                (do-pick
                 (lambda (pick-from unchosen result index how-many)
                   (cond ((zero? how-many)
                          result)
                         ((null? pick-from)
                          (do-pick (if (null? unchosen)
                                       elements
                                       unchosen)
                                   '()
                                   result
                                   index
                                   how-many))
                         ((zero? index)
                          (do-pick (cdr pick-from)
                                   unchosen
                                   (cons (car pick-from)
                                         result)
                                   (new-index (+ (length (cdr pick-from))
                                                 (length unchosen)))
                                   (- how-many 1)))
                         (else
                          (do-pick (cdr pick-from)
                                   (cons (car pick-from)
                                         unchosen)
                                   result
                                   (- index 1)
                                   how-many))))))
        (do-pick elements '() '() (random-integer list-length) how-many))))

Credit @tmtvl

Usage

(pick (iota 100)
	  100)
;; (79 28 18 40 90 29 30 66 80 36 23 34 42 84 25 35 88 54 15 92 69 8 2 16 95 27 19
;; 74 91 77 68 3 83 57 26 86 89 60 53 47 21 85 72 0 73 96 93 58 32 10 ....)
(pick '(A C G T)
         5)
;; (T G A T C)
(+ (car (pick (iota 6)
			  1))
   1)
;; 1

[Recipe] Topological sort

Problem

Given a directed acyclic graph (DAG) describing how nodes depend on other nodes, visit the nodes in an order where all dependencies of each node are visited before that node. This algorithm is known as topological sort.

For example, the Unix make program does a topological sort to figure out the build order for the files.

Solution

Public domain, from Shiro Kawai: https://github.com/shirok/Gauche/blob/master/lib/util/toposort.scm

[Recipe] Convert integer to list of digits

Problem

You have a number with more than one digits, and you want a list with all its digits as numbers.

Solution

(define (number->list number)
  (let ((chars (string->list (number->string number))))
    (map (lambda (char)
           (string->number (string char)))
         chars)))

Credit @jcubic

Usage

(number->list 10001)
;; ==> '(1 0 0 0 1)

void value

I've seen this pattern in a book by Nils M Hom Sketchy Scheme. it can be written as a variable or as a macro:

(define void (if #f #f))

(define-syntax void
  (syntax-rules ()
    ((_) (if #f #f))))

This is a way to get undefined (or to be precise unspecified by R7RS spec) value in Scheme.
But I'm not sure how much it's compatible with different scheme systems.

we can also add predicate:

(define (void? x)
  (equal? (if #f #f) x))

(define (void? x)
  (equal? x void))

Tested in Chicken, Guile, and Kawa (that gives warning warning - missing else where value is required).

Add compatibility chart

It seems that different Scheme implementation works differently in terms of loading R7RS. If we have R7RS code that doesn't work the same out of the box we should list all major implementations and show how to use the code.

An example is bytevector? in Chicken Scheme discussed in #46 also guile use some other code to use R7RS otherwise some of the code may don't work.

I would add a table (markdown support tables, at least on GitHub) with a matrix:

Scheme file REPL
Chiken ... ...
Guile ... ...
Kawa ... ...

[Recipe] Format Unix timestamp

Problem

Given an integer saying the number of seconds since the Unix epoch (1970-01-01 00:00:00) format it as a human-readable date and time string.

Solution

SRFI

Using SRFI-19

(define (time-unix->time-utc seconds)
  (add-duration (date->time-utc (make-date 0 0 0 0 1 1 1970 0))
                (make-time time-duration 0 seconds)))

(define (time-unix->string seconds . maybe-format)
  (apply date->string (time-utc->date (time-unix->time-utc seconds))
         maybe-format))

Credit @weinholt

Usage

; Loko
> (time-unix->string 946684800)
"Sat Jan 01 00:00:00Z 2000"
; Chez
> (time-unix->string 946684800)
"Sat Jan 01 02:00:00+0200 2000"
; Guile
> (time-unix->string 946684800)
$1 = "Sat Jan 01 01:00:00+0100 2000"

Return type of the object

I have this function in my interpreter, but it does a lot more since it also returns types for JavaScript objects and also internal implementation objects.

Here is something for R7RS scheme:

(define (type obj)
  (cond ((number? obj) "number")
        ((string? obj) "string")
        ((symbol? obj) "symbol")
        ((vector? obj) "vector")
        ((pair? obj) "pair")
        ((null? obj) "nil")
        ((procedure? x) "procedure")
        ((char? x) "character")
        ((boolean? obj) "boolean")))

Unfortunately, there is no way to test if an object is a record type.

@lassik Should we include functions like this or is it too simple? I think it would be nice to have this function so you don't need to write it yourself and can just copy/paste.

Publishing the cookbook

@jcubic Should we store the recipes as Markdown files in Git, and write some Scheme code to turn them into HTML and upload them on the server?

We should also decide on the URL - cookbook.scheme.org or doc.scheme.org/cookbook?

Setup CI/CD for the Pull requests

It would be nice to have GitHub workflow that will build and upload the file to the server. It will make it easier to manage. When this is done for this repo it could be replicated for different repositories.

This is continuation of discussion in #43

Naming conventions

We should probably decide on common names to use for variables in similar situations. For example:

  • Name to use for list argument to procedure (or lis or lst) by default?
  • Name to use for first list element and the rest: (let ((elem (car list)) (rest (cdr list))) ...)
  • Name to use for loops: (let loop ...)?
  • I often use inner and outer with nested loops.
  • loop and recurse are good ways to differentiate proper tail recursion (needs O(1) stack space) from general recursion (O(n) stack space).
  • ...

Haskell has a convention of using x for a list element, and xs for the list. xs is meant as the plural of x: "multiple x's". They also use y and ys.

[Recipe] Join list of strings with delimiter

Problem

You have a list of strings and string delimiter and you want to create a single string from the list with a delimiter in between

Solution

(define (string-join list delimiter)
  (let iter ((list list) (result '()))
    (if (null? list)
        (apply string-append (reverse result))
        (let ((item (car list))
              (rest (cdr list)))
          (if (null? result)
              (iter rest (cons item result))     
              (iter rest (cons item (cons delimiter result))))))))

Credit @jcubic

Alternative

Alternative using string ports

(define (string-join list delimiter)
  (if (null? list) ""
      (call-with-port (open-output-string)
                      (lambda (out)
                        (write-string (car list) out)
                        (for-each (lambda (item)
                                    (write-string delimiter out)
                                    (write-string item out))
                                  (cdr list))
                        (get-output-string out)))))

Credit @lassik

SRFI

you can use SRFI-13 that provides string-join function.

Usage

(string-join '("foo" "bar" "baz") ":")
;; ==> "foo:bar:baz"

[Recipe] Thread pool

Problem

I have a task queue and I want to split the work among multiple threads. There can be many more tasks than threads.

Solution

The mailbox library is Chicken only, but any other thread-safe queue could be used instead.

(import (srfi 18))
(cond-expand (chicken (import (mailbox))))

(define (map-iota proc n)
  (let loop ((new-list '()) (i 0))
    (if (= i n) (reverse new-list)
        (loop (cons (proc i) new-list) (+ i 1)))))

(define thread-pool-size (make-parameter 2))

(define (run-thread-pool thunks)
  (let ((mailbox (make-mailbox)))
    (define (worker)
      (let loop ()
        (let ((thunk (mailbox-receive! mailbox 0 (eof-object))))
          (unless (eof-object? thunk)
            (thunk)
            (loop)))))
    (define (index->thread-name i)
      (string (integer->char (+ i (char->integer #\A)))))
    (for-each (lambda (thunk) (mailbox-send! mailbox thunk))
              thunks)
    (let ((threads (map-iota (lambda (i)
                               (make-thread worker (index->thread-name i)))
                             (thread-pool-size))))
      (for-each thread-start! threads)
      (let loop ()
        (unless (mailbox-empty? mailbox)
          (thread-sleep! 0.1)
          (loop)))
      (for-each thread-join! threads))))

Credit: @lassik

Usage

Overwrite * and + to work like in Python

Someone askes on /r/lisp Sub Reddit: Python's approach is much better {{{ x= ( 10 * [a] ) }}} because i don't have to remember the (ad-hoc) name of the function.

I think it would be fun to create code that will make Scheme work like Python. examples of python code:

[1] * 2 * 4 * 5
2 * [2] * 3 * 3
"x" * 2 * 3
10 * "x" * 10 * 2

you can put sequence (here list or string) and use multiplication to make the sequence larger. And + works by concatenation of the sequences.

Here is my quick code for two arguments:

(define * (let ((mul *))
            (lambda args
              (if (= (length args) 2)
                  (let ((first (car args))
                        (second (cadr args)))
                    (cond ((and (list? first) (integer? second))
                           (apply append (make-list second first)))
                          ((and (list? second) (integer? first))
                           (apply append (make-list first second)))
                          (else
                           (apply mul args))))
                  (apply mul args)))))

scheme> (* '(1 2) 10)
;; ==> (1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2)
scheme> (* 1 2)
;; ==> 2

I think it would be a nice exercise to make * work exactly like in Python with lists, vectors, and strings. and It would be a good example that will show that Scheme is more powerful than python because you can do exactly the same what python have in Scheme but not other way around.

What do you think about including a recipe something like "Python * and + in Scheme" or something similar?

NOTE: if you don't like overwriting builtins it was used by Gerrald Sussman in his talk We Really Don't Know How to Compute! from 2011 Strange Loop.

[Recipe] Split list into groups that are equal judging by a procedure

Problem

Write a procedure (group-by f lst) that splits the list lst into sublists. The sublist boundaries are determined by the value of (f x) for each element x in lst. When the value changes, a new sublist is started from that element. Values of (f x) are compared by equal?.

Return a list of the groups, i.e. each group is a sublist in the main list.

Solution

(define (group-by f lst)
  (if (null? lst) '()
      (let ((first (car lst)))
        (let loop ((lst (cdr lst)) (key (f first)) (g (list first)) (gs '()))
          (if (null? lst)
              (reverse (cons (reverse g) gs))
              (let ((newkey (f (car lst))))
                (if (equal? key newkey)
                    (loop (cdr lst) key
                          (cons (car lst) g)
                          gs)
                    (loop (cdr lst) newkey
                          (list (car lst))
                          (cons (reverse g) gs)))))))))

Credit: @lassik

Usage

(group-by even? (iota 10))
;; ==> ((0) (1) (2) (3) (4) (5) (6) (7) (8) (9))

(group-by odd? '(1 3 5 2 1 6 4 1 7))
;; ==> ((1 3 5) (2) (1) (6 4) (1 7))

(group-by string-length '("aa" "bb" "ccc" "dddd" "eeee" "ffff" "g" "h"))
;; ==> (("aa" "bb") ("ccc") ("dddd" "eeee" "ffff") ("g" "h"))

(group-by (lambda (i) (truncate-quotient i 3)) (iota 20))
;; ==> ((0 1 2) (3 4 5) (6 7 8) (9 10 11) (12 13 14) (15 16 17) (18 19))

Add Scheme header to index and recipes

It would be nice to have the scheme.org menu on the individual cookbook pages. I'm afraid I cannot test this, but would something like the following hand-written changes to www.scm work?

First, define the header:

(define header '(header 
                  (ul (@ (class "menu")) 
                      (li (a (@ (href "https://www.scheme.org")) "Home"))
                      (li (@ (class "active")) "Docs")
                      (li (a (@ (href "https://community.scheme.org")) "Community"))
                      (li (a (@ (href "https://standards.scheme.org")) "Standards"))
                      (li (a (@ (href "https://implementations.scheme.org")) "Implementations")))))

Add to main page by changing line 127 to splice in the header:

`(,header (h1 (@ (id "logo")) "Scheme Cookbook")

And add to each recipe by changing line 163 to splice in the header:

     `(,header ,@(recipe-sxml recipe)

[Recipe] check if all elements are the same

Problem

Check if all elements are the same using a predicate.

Solution

I'm not sure if there is already a utility like this in some of SRFI. But there are few solutions on StackOverflow, but they use equal? instead of predicate: Scheme: How to check if all elements of a list are identical.

I came up with a solution using fold:

(define (ordered? fn . args)
  (let ((first (car args)))
    (fold (lambda (arg result)
            (and result (equal? arg first)))
          #t
          (cdr args))))

Credit: @jcubic

Usage

It can be used to check if strings are sorted (for R5RS implementation where string comparators accept 2 arguments) or check if all elements are equal. You can also check if any sequence is monotonous.

(ordered? eq? 1 1 1 1 1 1)
(define (identical? . args)
   (apply ordered? eq? args))

(identical? 1 1 1 1 1 1 1)
;; ==> #t
(identical? 1 1 1 1 1 1 0)
;; ==> #f

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.