hunter ###Admin 2019-03-11 09:58:43 No. 5
Using Chez Scheme as a Shell
For a while now I've wanted to use Scheme as my shell. There have been a bunch of projects with a similar goal, but they all do one thing wrong: they try to keep shell syntax. They try to make it easy to run shell commands alongside a more general programming language and they end up with some Frankenstein language that's in between. I didn't want any shell, I wanted pure Scheme.
I felt that existing Scheme implementations were lacking some necessary features mostly tab-completion of filenames, but a few other things so I began work on my own. I learned a lot and I felt that it was going well. However, a few months ago I found that Chez Scheme supported tab-completion of filenames out of the box; so I decided to try it out.
I think that Scheme handles complex commands much better than bash does. As an example, recently I wanted to add foreign subtitles to a TV show I had. I had the foreign subs, but the timing was off so I decided to extract the native subtitles and use their timing. I wanted the extracted subtitles to share the same name as the video, but in bash this is a little wonky. It is certainly possible to do, but I can never remember the syntax Can you? Every time I do anything more complex than run a command in bash I have to look up the syntax for it. To make it a bit harder, I had manually done this for the first video and added the subtitles so I needed to ignore two of the videos. In Scheme this is trivial:
;; We use the threading macro, like a bash pipe (-> (ls) ;; Grab a list of files in the current directory ;; select only files that end with mkv ((lambda (files) (filter (lambda (file) (string-ends-with? file "mkv")) files))) ;; ignore the first two files that were done manually (cdr) (cdr) ((lambda (files) ;; run mkvextract on each video selecting only the subtitles (track 4) (for-each (lambda (file) (run-command "mkvextract" file "tracks" "name the subtitles VIDEO-NAME.ass (string-append "4:" ;; remove "mkv" from the file name (substring file 0 (- (string-length file) 3)) "ass"))) files))))
Really I think that it is very nice. The only really bad aspect is the lack of libraries. The standard library is abysmal, I'm used to writing Rust code with a very good standard library and crates.io for everything else. With Scheme, I had to write procedures like
string-begins-with myself. There also really aren't third party libraries. I was able to use irregex which is quite nice, but that's about it.
Another problem is that tab completion isn't very spectacular. It works okay for procedure names and filenames I think it works as bash does, but I'm used to zsh where I can type
~/P/P<tab> and it expands to
~/Programming/Projects. but there isn't any help with arguments. I found that I kept forgetting how many arguments a procedure expected and what order the arguments were. Types might solve some of this, but a more general solution might be to display the unfilled arguments' names. Idris can do some interesting things with autocompletion too. Some of this might just be a lack of familiarity with the system.
My threading macro is also quite annoying. It places the previous result as the first argument to the next procedure; if you need it in a different position you have to wrap the procedure in a lambda, and the lambda expression must be wrapped in parentheses such that it is being applied. Racket has a threading macro that avoids these issues, allowing you to specify the argument position with an underscore. It might be possible to do this in Scheme but I'm not very good with macros.
I'm not really using this as my daily shell. What I've been doing is firing it up when I need to do something complex and I've found that this gives good results.
What I'd really like to see is for Racket to support tab-completion of filenames. Racket has a great threading macro, a good standard library, and a good community around it. Racket is in the process of migrating to Chez Scheme, so I was hoping they would gain support. Having checked the current build of RacketCS there is not support for completion of filenames which is disappointing.
I might continue to tinker with it, but I don't really think UNIX systems are suited for user interaction. I think that I will work towards greener pastures.