OCaml Weekly News
Hello
Here is the latest OCaml Weekly News, for the week of November 29 to December 06, 2022.
Table of Contents
Optimising an interpreter
Continuing the thread from last week, yallop said
I could use MetaOCaml and do staged metaprogramming. Should be easy and achieve a very respectable speedup but MetaOCaml is obscure so I assume dev tools would no longer work?
The tools mostly work.
For example, ocamlfind (with this package, which is installed automatically) and dune (see, e.g., this example) both support MetaOCaml.
I don’t know how well Merlin works with MetaOCaml, but it might work reasonably well if you use attributes rather than quotation
syntax (i.e. (2 + 3) [@metaocaml.bracket]
rather than .<2 + 3>.
).
Libraries mostly work unmodified (since MetaOCaml is binary-compatible with OCaml), but a few libraries conflict with MetaOCaml’s
additional syntax (e.g. by using >.
for float comparison) and need minor modifications.
Upcoming Dune Feature: (include_subdirs qualified)
Rudi Grinberg asked
Editor’s note: there were many replies, please follow the link above to read them.
Hello, the dune team is working on adding (include_subdirs qualified)
support to dune and would like your feedback on some user
facing details. I’ll explain how the feature works in this post, but you can also read the initial feature
request for some background.
Wrapped Libraries
First, let’s review how wrapped libraries work. This is important because (include_subdirs qualified)
just generalizes the scheme
to arbitrary directories. Suppose we have the following library:
(library (wrapped true) ;; this is the default. it's added here for clarity (name foolib))
By default, dune will make every single module in this library available under Foolib
- e.g. Foolib.X
. In this example, the
“library interface” module is Foolib
and it is always present. In this example, it is generated. But it can also be written by
hand:
$ cat foolib.ml (* We can choose to export whatever we want *) module X = X
The advantage of hand writing this interface module is of course tighter control over the interface of the library. The disadvantage is that it has to be manually written.
Qualified Subdirectories
The stanza (include_subdirs qualified)
generalizes the above scheme. In particular, one may introduce a directory with modules.
For example:
$ ls foo.ml sub/ x.ml y.ml
Inside foo.ml, we’ll refer to Sub.X
and Sub.Y
. While x.ml
and y.ml
we’ll be able to refer to each other in an unqualified
manner (X
and Y
). Naturally, the module Sub
will also be an interface module and the user will have the option to write it
manually. This is where we get some options.
Interface Modules for module groups
Given the example above, where should the user write the interface module for Sub
and how should it be referred to in dune files?
I’ll list two options and briefly describe their advantages:
sub/sub.ml
- this would be most similar to how we handle the toplevel library interface. It also maintains the invariant that every directory has at most one interface file.sub.ml
- this module would live in the same directory assub/
and would allowsub/sub.ml
to exist asSub.Sub
. I think this behavior is more intuitive to users.
Finally, how should we refer to such modules in dune files? For example, in dune files we can set per module preprocessing or mark
some modules as private. How should we make the Sub
private?
(library (name foolib) (private_modules foo) ;; or this (private_modules foo.foo))
If the interface modules exists at sub/sub.ml
, then we should probably just forbid foo.foo
. While if the interface module is
sub.ml
, both paths are allowed and simplify refer to different modules (sub.ml
or sub/sub.ml
).
My questions to the community:
- Which scheme do you think is more natural?
- Do you have any other comments about the feature?
AoC 2022 by TJ DeVries
Emile Rolley announced
New series of TJ DeVries on Advent of Code with OCaml :slight_smile:
https://youtube.com/playlist?list=PLep05UYkc6wQwd0-XPGQ5gRHID8YMYIeW
ptime 1.1.0 and mtime 2.0.0
Daniel Bünzli announced
I’d like to announce new releases of ptime and mtime. Ptime and mtime provide types and clocks for POSIX and monotonic time. A few highlights:
- Ptime’s non-strict RFC 3339 parsing is further relaxed to parse
hhmm
andhh
time zones (strict ishh:mm
). This is sometimes found in web services. Strictly speaking it means a larger subset of ISO 8601 is parsed. Other than that the release adds a few more convenience API entry points for dealing with years and weekdays, see the [release notes][ptime-rn] for all the details. - On Linux and MacOS
Mtime_clock
does now take into account system sleep time by respectively usingCLOCK_BOOTTIME
andmach_continuous_time
(thanks to @BikalGurung for the patch). - The
Mtime
module suffered a bit of floating point conversion mess and bugs (thanks to @talex5 for reporting). The functions deprecated in 1.4.0 have now been removed[^1] and the single point of conversion with floats are theMtime.Span.{to,of}_float_ns
functions.
Other changes are described in the release notes for ptime
and mtime
.
- Home pages: ptime, mtime
- Docs & manuals: ptime, mtime or
odig doc ptime mtime
- Install:
opam install ptime mtime
Best,
Daniel
[^1]: It seems that this lead to quite a bit of breakage on the opam-repository
. Sorry about that I didn’t know there were so many
packages using mtime
.
A big thanks to my donators. I welcome and thank the new donator @jhw.
Cool OCaml-based web-frontend for Emacs org-files
Kiran Gopinathan announced
Hey everyone! It’s been a while. Here’s a little thing I’ve been working on that I thought might be interesting for this community: Emacs-viewer
This is a simple application that presents a web frontend for your org files in Emacs. Internally, it’s written using Dream for the server and Bonsai for the web frontend.
The high level idea is that the backend uses the emacsclient
command to interact with your emacs
process, using it to parse and
process an org-mode buffer. The data from emacs
, which is returned as a s-expression, is parsed by the backend into a common
representation shared between the backend and the frontend. The frontend then renders this in a prettified format.
Wrapping my head around Bonsai took me a bit of time - somewhat suprisingly, even though I’ve spent a bit of time hacking on
note
-based web frontends, bonsai
’s encoding ends up necessitating quite a different style of development and requires organising
components in fairly different ways to which I was used to.
When I get the time I hope to write up a blog post on a quick start on both of these frameworks, because I haven’t found a satisfactory zero-to-working-example guide for either of these frameworks
Anyway, happy to field any questions, or feel free to look at the code (more gifs at the repo):
https://github.com/Gopiandcode/emacs-viewer
Here’s another cool gif of a tag-based search field, which was probably my first actually reusable non-trivial bonsai component:
Brr 0.0.4
Daniel Bünzli announced
There’s a new release of brr. brr is a toolkit for programming browsers with the js_of_ocaml compiler.
An important note and a few highlights:
Important. The js_of_ocaml devs are making good progress towards support for OCaml 5 effects (yay!). A side effect (haha) is that the way
brr
’s cookbook suggested to callback from JavaScript to OCaml breaks – my fault. It also entails a change in the API to listen and unlisten events. See this section of the release notes and the updated cookbook for callbacks and exposing OCaml to JavaScript.brr
itself has been sanitized, thanks to @vouillon’s for his help. If you created bindings to callback APIs or exposed OCaml functions to JavaScriptgit grep
for yourJv.repr
usages and replace them with the newJv.callback
function. If you didn’t and just usedbrr
’s APIs you have nothing to do.- New bindings to Web MIDI.
- WebWorker safe
brr
. You can now safely link any of brr’s module in a WebWorker and it won’t explode in your face. Previously we had a few toplevel module initialization bits that were accessing stuff that is not available in WebWorkers.
Other than that there are the little additions and tweaks here and there, all of which you can read about in the release notes.
Home page, Docs and manuals or odig doc brr
Install: opam install brr
Best,
Daniel
A big thanks to my donators.
OpenAI OpenGPT writing OCaml
Boning announced
Editor’s note: this long message started an even longer thread. Please follow the link above to read it.
Not sure whether this is the right place for this. But I really want to share this experience with you guys.
TLDR; OpenGTP is chatbot developed by OpenAI. It is NOT tailored for OCaml code generation or even coding. It understand natural
language and context. It knows how to write custom types and use third party APIs (from Lwt
to Cohttp
to Str
to even
Ppxlib
). It can take feedback and correct error on error messages. The final code does not work appropriately, but it is very
close and compiles (on a different thread, I managed to get it to use Str
which works properly). It also struggle a little bit on
dune vs dune-project.
Please just read the full and unedited log. Please remember this is with a bot. What is your thought on this?
What is Python requirements.txt and please provide an example
Python requirements.txt is a file that lists the Python packages that your application depends on. It allows you to easily install all of the necessary packages for your project by running a single command, rather than having to install each package individually.
Here is an example of a requirements.txt file:
numpy==1.18.5 pandas==1.0.5 flask==1.1.2
This file specifies that the project requires three packages: numpy, pandas, and flask. The version number is specified after the package name and is separated by an equal sign. This ensures that the exact versions of the required packages are installed.
To install the packages listed in a requirements.txt file, you can use the pip
command-line tool. For example, if your
requirements.txt file is in the current directory, you can install all of the required packages by running the following command:
pip install -r requirements.txt
This command will install the numpy, pandas, and flask packages, as well as any other packages that those packages depend on. This makes it much easier to manage the dependencies for your Python project.
Please write a function to parse it in OCaml
Here is an example of a function that can parse a requirements.txt file in OCaml:
let parse_requirements (contents: string) : (string * string) list = let lines = String.split_on_char '\n' contents in List.filter (fun line -> line <> "") lines |> List.map (fun line -> match String.split_on_char '==' line with | [package; version] -> (package, version) | _ -> raise (Invalid_argument "Invalid requirements.txt format") )
This function takes a string containing the contents of a requirements.txt file and returns a list of tuples, where each tuple contains the name of a package and its version number. It splits the contents of the file on newline characters to get a list of individual lines, filters out any empty lines, and then uses pattern matching to extract the package name and version number from each line.
Here is an example of how you could use this function:
let requirements = "numpy==1.18.5 pandas==1.0.5 flask==1.1.2" let packages = parse_requirements requirements (* packages will be a list containing the following tuples: ("numpy", "1.18.5") ("pandas", "1.0.5") ("flask", "1.1.2") *)
This function can be easily modified to handle different formats for the requirements.txt file, such as specifying package versions using a different delimiter or allowing for version ranges.
Please use a custom record and make version optional
Here is an example of a function that can parse a requirements.txt file in OCaml and uses a custom record type to store the package name and version number:
type package = { name: string; version: string option; } let parse_requirements (contents: string) : package list = let lines = String.split_on_char '\n' contents in List.filter (fun line -> line <> "") lines |> List.map (fun line -> match String.split_on_char '==' line with | [name; version] -> { name; version = Some version } | [name] -> { name; version = None } | _ -> raise (Invalid_argument "Invalid requirements.txt format") )
This function is similar to the previous example, but instead of returning a list of tuples, it returns a list of package
records.
The package
record has two fields: name
, which is a string containing the name of the package, and version
, which is an
optional string containing the version number of the package.
Here is an example of how you could use this function:
let requirements = "numpy==1.18.5 pandas==1.0.5 flask==1.1.2" let packages = parse_requirements requirements (* packages will be a list containing the following records: { name = "numpy"; version = Some "1.18.5" } { name = "pandas"; version = Some "1.0.5" } { name = "flask"; version = Some "1.1.2" } *)
This function can be easily modified to handle different formats for the requirements.txt file, such as specifying package versions using a different delimiter or allowing for version ranges.
What is the dune file of this
The dune file is a configuration file used by Dune, a build system for OCaml. It specifies the structure and dependencies of an OCaml project, as well as the build rules and flags that should be used.
Here is an example of a dune file for a simple OCaml project:
(executable (name my_project) (libraries stdio) (preprocess (pps ppx_deriving.std)))
This dune file defines an executable named my_project
that depends on the stdio
library and uses the ppx_deriving.std
preprocessor. It also specifies that the project should be built using the default build rules and flags.
You can use the dune build
command to build the project defined in this dune file. This will compile the source code, link the
resulting object files, and produce an executable binary file.
The dune file for a project can be quite complex, with multiple libraries and executables, custom build rules and flags, and support for other languages and tools. However, the basic structure is similar to the example above.
Please write a pretty printer of type package list
Here is an example of a function that can pretty-print a list of package
records in OCaml:
let print_package_list (packages: package list) : unit = List.iter (fun { name; version } -> match version with | Some v -> Printf.printf "%s==%s\n" name v | None -> Printf.printf "%s\n" name ) packages
This function takes a list of package
records and iterates over each record, printing its name and version number (if it has one).
It uses the Printf.printf
function to print each package in the format used by requirements.txt files, with the package name
followed by the version number (if specified) and separated by an equal sign.
Here is an example of how you could use this function:
let packages = [ { name = "numpy"; version = Some "1.18.5" }; { name = "pandas"; version = Some "1.0.5" }; { name = "flask"; version = None }; ] print_package_list packages (* This will print the following to the console: numpy==1.18.5 pandas==1.0.5 flask *)
This function can be easily modified to handle different formats for printing the package records, such as using a different delimiter for the version number or omitting the version number altogether.
Can you write a test for parse_requirements
Here is an example of a unit test for the parse_requirements
function in OCaml using the OUnit2
testing framework:
open OUnit2 let parse_requirements = (* code for parse_requirements goes here *) let tests = [ (* Test parsing of a requirements.txt file with three packages and version numbers *) "three-package-file" >:: (fun _ -> let requirements = "numpy==1.18.5 pandas==1.0.5 flask==1.1.2" in let expected = [ { name = "numpy"; version = Some "1.18.5" }; { name = "pandas"; version = Some "1.0.5" }; { name = "flask"; version = Some "1.1.2" }; ] in assert_equal (parse_requirements requirements) expected ); (* Test parsing of a requirements.txt file with three packages and no version numbers *) "three-package-file-no-versions" >:: (fun _ -> let requirements = "numpy pandas flask" in let expected = [ { name = "numpy"; version = None }; { name = "pandas"; version = None }; { name = "flask"; version = None }; ] in assert_equal (parse_requirements requirements) expected ); (* Test parsing of an empty requirements.txt file *) "empty-file" >:: (fun _ -> let requirements = "" in let expected = [] in assert_equal (parse_requirements requirements) expected ); ] let suite = "parse_requirements_test_suite" >::: tests let _ = run_test_tt_main suite
This test suite defines three unit tests for the parse_requirements
function: one that tests parsing a requirements.txt file with
three packages and version numbers, one that tests parsing a requirements.txt file with three packages and no version numbers, and
one that tests parsing an empty requirements.txt file. Each test uses the assert_equal
function to compare the result of calling
parse_requirements
with the expected result and reports any differences.
To run this test suite, you can use the dune runtest
command. This will compile the test suite and run all of the tests, printing
the results to the console. If any of the tests fail, the output will indicate which tests failed and why.
What is the dune file of this test
Here is an example of a dune file for the unit test suite described above:
(executable (name parse_requirements_test) (libraries oUnit) (preprocess (pps ppx_deriving.std)))
This dune file defines an executable named parse_requirements_test
that depends on the oUnit
library and uses the
ppx_deriving.std
preprocessor. It specifies that the test suite should be built using the default build rules and flags.
To build and run the test suite, you can use the following commands:
dune build parse_requirements_test.exe ./_build/default/parse_requirements_test.exe
This will compile the test suite and produce an executable binary file, which you can then run to execute the tests. The output of the tests will be printed to the console, indicating whether the tests passed or failed.
Please also provide a dune-project
file for this
Here is an example of a dune-project
file for the OCaml project that includes the parse_requirements
function and the unit test
suite described above:
(lang dune 2.0) (name parse_requirements) (library (name parse_requirements) (public_name parse_requirements)) (executable (name parse_requirements_test) (libraries oUnit parse_requirements) (preprocess (pps ppx_deriving.std))) (rule (targets parse_requirements.install) (action (progn (install parse_requirements) (install parse_requirements_test))))
This dune-project
file defines a library named parse_requirements
that contains the code for the parse_requirements
function,
and an executable named parse_requirements_test
that contains the unit test suite for the parse_requirements
function. It also
specifies that the parse_requirements
library should be installed when the parse_requirements.install
target is built.
To build and install the parse_requirements
library and the parse_requirements_test
executable, you can use the following
commands:
dune build parse_requirements.install dune install parse_requirements.install
This will compile the parse_requirements
library and the parse_requirements_test
executable, and then install the
parse_requirements
library to the default location. You can then use the parse_requirements
library in other OCaml projects by
adding it as a dependency in the dune-project
file and running dune build
to build the project. You can also run the
parse_requirements_test
executable to test the parse_requirements
function.
I am getting error “File ”parse_requirements_test.ml“, line 12, characters 34-35: 12 | match String.split_on_char ‘==’ line with ^ Error: Syntax error”
The error you are seeing is likely due to a syntax error in the parse_requirements
function. In OCaml, strings must be surrounded
by double quotes ("
), not single quotes ('
). It looks like the String.split_on_char
function is being called with a
single-quoted character, which is causing a syntax error.
Here is the corrected version of the parse_requirements
function that should fix the syntax error:
let parse_requirements (contents: string) : (string * string) list = let lines = String.split_on_char '\n' contents in List.filter (fun line -> line <> "") lines |> List.map (fun line -> match String.split_on_char '==' line with | [package; version] -> (package, version) | _ -> raise (Invalid_argument "Invalid requirements.txt format") )
In this version of the parse_requirements
function, the character passed to the String.split_on_char
function is a double quote
("
), which is the correct syntax in OCaml. This should fix the syntax error you were seeing.
If you are still experiencing errors, please let me know and I will try to help you further.
But split_on_char takes a char instead of string
You are correct that the String.split_on_char
function takes a char
argument, not a string
argument. In OCaml, a char
is a
single character, represented by a single quote followed by the character (e.g. 'a'
, 'b'
, 'c'
).
In the previous version of the parse_requirements
function, the String.split_on_char
function was called with a single-quoted
character ('=='
), which is not a valid char
value in OCaml. This caused a syntax error.
To fix this error, you can simply pass the '='
character to the String.split_on_char
function instead. This will split the
string on the =
character, which is the delimiter used in requirements.txt files to separate the package name and version number.
Here is the corrected version of the parse_requirements
function:
let parse_requirements (contents: string) : (string * string) list = let lines = String.split_on_char '\n' contents in List.filter (fun line -> line <> "") lines |> List.map (fun line -> match String.split_on_char '=' line with | [package; version] -> (package, version) | _ -> raise (Invalid_argument "Invalid requirements.txt format") )
In this version of the parse_requirements
function, the String.split_on_char
function is called with the '='
character, which
is a valid char
value. This should fix the error you were seeing.
I hope this helps! Let me know if you have any other questions.
Old CWN
If you happen to miss a CWN, you can send me a message and I’ll mail it to you, or go take a look at the archive or the RSS feed of the archives.
If you also wish to receive it every week by mail, you may subscribe online.