Posts tagged lisp


Scribble hacks: insert module code

:: dev, lisp

By: Maciej Barć

get-module-content for showing module implementation

Normally Scribble, the Racket’s documentation tool, does not provide a way to see how a function/object/module is actually defined in the code. Just recently I wound a good-enough way to insert the whole content of the module we are documenting with Scribble.

Here’s my implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

The input is a module name in the form that you would use in absolute require form, except that it is a string, not a bare syntax.

get-module-content will reach out to Your Racket package database and find a module that you gave. It will read it and remove all comments. It returns a Racket string.

The string? returned by get-module-content can later be used to either extract some more info but it can be effectively used to insert the full module implementation block.

get-module-content usage inside Scribble

In Scribble I insert the module source code like so:

1
2
3
The source code of this module:

@codeblock[@get-module-content{mypkg/utils/path}]

See also

Using gzexe for shipping Racket executables

:: dev, lisp, tutorial

By: Maciej Barć

Racket executables made by raco exe are known to be quite large. One of tools that can be used to help reduce the size of produced binaries is the gzexe program.

gzexe is a tool that can compress a executable binary. It can be acquired by installing gzip on most Linux distributions (included in the app-arch/gzip package on Gentoo).

Creating a hello-world executable with Racket

Write following contents to hello-world.rkt file:

1
2
3
4
5
6
7
#lang racket/base

(define (main)
  (displayln "It REPLs. Ship it!"))

(module+ main
  (main))

To make a binary run:

1
raco exe --orig-exe -v -o hello-world hello-world.rkt

The file hello-world will be produced.

This is what file hello-world says about it:

1
2
3
hello-world: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
for GNU/Linux 3.2.0, stripped

This “small” executable weights 46 MB!

In comparison busybox weights around 2 MB.

Compressing with gzexe

Keep in mind that gzexe will overwrite the compressed file and create a backup with appended "~".

1
gzexe hello-world

And this gives us only 8,5 MB. Nice!

In comparison bazel, which is a single-binary build system written in JAVA, executable takes 33 MB on my Gentoo machine. I tried compressing it with gzexe and it reduces it only by 10%, to around 29 MB.

gzexeis not a silver bullet but with Racket exes it works very nicely.

ELisp ebuilds good practices

:: emacs, gentoo, lisp, packaging

By: Maciej Barć

Check load path

Some Elisp package compilation failures are caused by not setting the loadpath correctly. It mostly happens when you compile source from a directory that is not the current working directory. For example:

1
elisp-compile elisp/*.el

In most cases you can cd or override the S variable to set it to location where ELisp source resides.

But in other cases you can append to load path the directory with source, see:

1
BYTECOMPFLAGS="${BYTECOMPFLAGS} -L elisp" elisp-compile elisp/*.el

Do not rename auto-generated autoload file

elisp-make-autoload-file allows to name the generated autoload file. For sake of easier debugging and writing Gentoo SITEFILEs, please do not rename the generated file.

The name of that file should always be ${PN}-autoloads.el.

Use new elisp-enable-tests function

elisp-enable-tests allows to set up IUSE, RESTRICT, BDEPEND and the test runner function for running tests with the specified test runner.

The 1st (test-runner) argument must be one of:

  • buttercup — for buttercup provided via app-emacs/buttercup,
  • ert-runner — for ert-runner provided via app-emacs/ert-runner,
  • ert — for ERT, the built-in GNU Emacs test utility.

The 2nd argument is the directory where test are located, the leftover arguments are passed to the selected test runner.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
EAPI=8

inherit elisp

# Other package settings ...

SITEFILE="50${PN}-gentoo.el"
DOCS=( README.md )

elisp-enable-tests buttercup test

Remove empty SITEFILEs

Recently a feature was added to elisp.eclass that will cause build process to generate the required SITEFILE with boilerplate code if it does not exist.

So if your SITEFILE looked like this:

1
(add-to-list 'load-path "@SITELISP@")

… then, you can just remove that file.

But remember to keep the SITEFILE variable inside your ebuild:

1
SITEFILE="50${PN}-gentoo.el"

Remove pkg.el files

The *-pkg.el files are useless to Gentoo distribution model of Emacs Lisp packages and should be removed. It is as simple as adding this line to a ebuild:

1
ELISP_REMOVE="${PN}-pkg.el"

Beware that some packages will try to find their ${PN}-pkg.el file, but in most cases this will show up in failing package tests.

Use official repository

It is tedious to repackage Elpa tarballs, so use the official upstream even if you have to snapshot a specific commit.

To snapshot GitHub repos you would generally use this code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# First check if we have the correct version to prevent
# autobumping package version without changing the commit.
[[ ${PV} == *_p20220325 ]] && COMMIT=65c496d3d1d1298345beb9845840067bffb2ffd8

# Use correct URL that supports snapshots.
SRC_URI="https://github.com/domtronn/${PN}/archive/${COMMIT}.tar.gz
    -> ${P}.tar.gz"

# Override the temporary build directory variable.
S="${WORKDIR}"/${PN}-${COMMIT}

Include live version support

We do not want to be worse than the Melpa unstable :D

So, why not allow the given package to be used live?

Even if you do not push the live package to the overlay, please include support for it.

1
2
3
4
5
6
7
8
if [[ ${PV} == *9999* ]] ; then
    inherit git-r3
    EGIT_REPO_URI="https://github.com/example/${PN}.git"
else
    SRC_URI="https://github.com/example/${PN}/archive/${PV}.tar.gz
        -> ${P}.tar.gz"
    KEYWORDS="~amd64 ~x86"
fi

Ask for tags

Git is good, git tags are good. In case if upstream does not tag their package or just forgets to, kindly ask them to create a git tag when bumping Emacs package versions.

Debugging Frog blog with syntax macros

:: blog, lisp, dev, tutorial

By: Maciej Barć

Constructing debugging syntax

I wanted to echo parameter values when I set them in my blog’s frog.rkt config file.

Nothing simpler in Racket!

First I create this macro for echoing a single parameter value when it is set:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(define-syntax-rule (verbose-set-parameter parameter-id parameter-value)
  (begin
    ;; Set the parameter.
    (parameter-id parameter-value)

    ;; Then call the parameter and print it's value.
    ;; The "'parameter-id" is special syntax
    ;; for turning a "parameter-id" identifier to a symbol.
    ;; We can also write it like:
    ;; > (quote parameter-id)
    ;; to be less confusing.
    (printf "[DEBUG] (~a ~v)\n" 'parameter-id (parameter-id))))

then, I create a wrapper for above macro that can take multiple parameter pairs:

1
2
3
4
5
(define-syntax-rule (verbose-set-parameters (parameter-id parameter-value) ...)
  (begin
    ;; Unpack a chain of "(parameter-id parameter-value)" pairs
    ;; using the "..." syntax.
    (verbose-set-parameter parameter-id parameter-value) ...))

Using the macro

Afterwards we can call it like so:

1
2
3
(verbose-set-parameters
 (current-title "XGQT's blog")
 (current-author "Maciej Barć"))

Notice that even the form of setting a parameter, that is (parameter-procedure "value"), remains the same, but in reality it is just similar to how the syntax macro pattern-matches on it.

Inspecting macro expansion

In racket-mode inside GNU Emacs we can inspect the macro expansion with racket-expand-region. Stepping through the expansion provided this result:

1
2
3
4
5
6
7
(begin
  (begin
    (current-title "XGQT's blog")
    (printf "[DEBUG] (~a ~v)\n" 'current-title (current-title)))
  (begin
    (current-author "Maciej Barć")
    (printf "[DEBUG] (~a ~v)\n" 'current-author (current-author))))

Comparing objects in Racket

:: dev, lisp, tutorial

By: Maciej Barć

Equality methods

By implementing a method for equality equal-to? and two extraction methods equal-hash-code-of and equal-secondary-hash-code-of we can define our own object comparison rules.

For more info see Object Equality and Hashing.

Consider the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(define integer%
  (class* object% (equal<%>)
    (super-new)

    (init-field [value 0])

    (define/public (equal-to? other-object recur)
      (= value (get-field value other-object)))

    (define/public (equal-hash-code-of hash-code)
      (hash-code value))

    (define/public (equal-secondary-hash-code-of hash-code)
      (hash-code value))))

If we create a new integer% object we can notice that it is not transparent (we can not inspect values of any of it’s fields).

1
2
(new integer%)
;;  => (object:integer% ...)

But if we compare two fresh integer% objects they will be equal.

1
2
(equal? (new integer%) (new integer%))
;;  => #true

Transparent class

A transparent cvlass is a class with the inspect expression valuye se to #false.

From Racket documentation Creating Classes:

Just as for structure types, an inspector controls access to the class’s fields, including private fields, and also affects comparisons using equal?.

Consider the following example:

1
2
3
4
5
6
7
8
(define integer%
  (class object%

    (super-new)

    (inspect #false)

    (init-field [value 0])))

If we create a new integer% object we can see it’s field values.

1
2
(new integer%)
;;  => (object:integer% 0)

And if we compare two fresh integer% objects they will be equal.

1
2
(equal? (new integer%) (new integer%))
;;  => #true

Mkdocs with Scribble

:: dev, lisp

By: Maciej Barć

Intro

Instead of changing CSS style for Your Racket projects documentation, You may be interested in compiling Markdown files generated form Scribble source into HTML documentation website.

Creating MkDocs project

  1. Create docs directory and mkdocs.yml config file in current directory, along with a dummy index.md file in docs folder.

    1
    mkdocs new .
    
  2. Edit the name of the project.

    Replace Racket-Project with your project name.

    1
    2
    ---
    site_name: Racket-Project
    

Building Scribble

Generate markdown files form scribble documentation.

Replace Racket-Project.scrbl with path to your scribble documentation main source file.

1
scribble --markdown --dest ./docs --dest-name index.md Racket-Project.scrbl

Building Markdown

Compile HTML documentation from the markdown source.

1
mkdocs build

HTML files should appear in the site directory.

Running the server

Some features, like search for example are only available when running the mkdocs server.

1
mkdocs serve

Caveats

Some scribble functions do not look good or work correctly for markdown-to-HTML compilation by MkDocs.

  • table-of-contents - looks like a source block

  • index-section - letter links do not work

Example configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
site_name: Racket-Ebuild
site_author: xgqt@riseup.net
site_description: library to ease ebuild creation
site_url: https://gitlab.com/gentoo-racket/racket-ebuild

repo_name: gentoo-racket/racket-ebuild
repo_url: https://gitlab.com/gentoo-racket/racket-ebuild

plugins:
  - search

theme:
  name: material

extra:
  social:
    - icon: fontawesome/brands/gitlab
      link: https://gitlab.com/gentoo-racket/racket-ebuild

Awesome Racket language features

:: dev, lisp

By: Maciej Barć

Also see: Fast-Racket at Racket's GitHub Wiki

Creating binaries

You can create portable binaries with Racket's raco command! Use raco exe and raco distribute.

More -> https://docs.racket-lang.org/raco/exe.html

Sample games

Racket provides a executable plt-games, when ran (from console) it opens a menu of miscellaneous games, among them: jewel, minesweeper, aces, spider, checkers. & more (20 games total).

Plots

You can plot data in 2d & 3d forms.

2D

Sample code:

1
2
3
4
5
6
#lang racket/base
(require racket/gui/base racket/math plot)

(plot-new-window? #true)

(plot (function sin (- pi) pi #:label "y = sin(x)"))

3D

Sample code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#lang racket/base
(require racket/gui/base racket/math plot)

(plot-new-window? #true)

(plot3d
 (surface3d (lambda (x y) (* (cos x) (sin y)))
            (- pi) pi (- pi) pi)
 #:title "An R × R  R function"
 #:x-label "x" #:y-label "y" #:z-label "cos(x) sin(y)")

Browser

There's a included library to render web pages, just "(require browser)".

Sample code:

1
2
3
4
#lang racket
(require browser)

(open-url "https://xgqt.gitlab.io/")

FFI

You can use Racket's Foreign Function Interface to interact with non-Racket libraries to make use of very fast libraries written in (mainly) FORTRAN & C.

For example sci uses FFI for CBLAS & LAPACK.

Parallelism

For greater speed up with parallel execution there are futures, places and distributed places (for distributed programming).