I labelled this as uncategorized as it’s somewhere between a question and a show-and-tell.
I was helping someone use knitr to embed and run Haskell code in Quarto.
The knitr code looks like this:
knitr::knit_engines$set(haskell = function(options) {
code <- options$code
codefile <- tempfile(fileext = ".hs")
on.exit(file.remove(codefile))
out <- system2(
file.path(path.expand('~'), '.ghcup/bin/ghc'),
c('-package text', '-e',"':script ", codefile, "'"),
stdout = TRUE
)
knitr::engine_output(options, code, out)
})
What this does is:
- Read a snippet of text into a temp file
- Run it with
ghc -package text -e “:script temp.hs”
So now you can run snippets like:
a = [2, 9, 6]
:{
updateSecond (x:_:z) y = x : y : z
updateSecond xs _ = xs
:}
updateSecond a 4
But you still need to put the braces around multiline strings or any I/O which makes it look a little ugly. We decided to create a script that parses the temp file and inserts the braces so you can instead just write.
a = [2, 9, 6]
updateSecond (x:_:z) y = x : y : z
updateSecond xs _ = xs
updateSecond a 4
This pattern looks like it’s generally useful for ghci-type scripts. In fact it might be a cool way to run quick scripts without a main function. So we threw together a program (originally in Haskell but used a LLM to translate it to AWK cause the process dependency makes it a little less convenient) to do the brace insertions.
Now we can do things like run a script without a main for quick prototyping etc.
x = [1..10]
y = [5..15]
import Data.List
z = sort (x ++ y)
print z
map (+2) z
import Data.Function
y' = z & drop 5 & reverse & drop 5 & reverse
print y'
doubleEveryOther :: [Int] -> [Int]
doubleEveryOther xs
| null xs = ys
| otherwise = zipWith ($) (cycle [id, (*2)]) xs
where
ys = [2,2,2]
doubleEveryOther []
So now you can do hscript test.hs and this runs fine. Is there a way to get the same behaviour (run an interpreted, potentially multiline script) without all this ceremony? If not it would be a very convenient feature to add since runhaskell still expects a lot more structure than a really quick script.
For some literate programming this ends up being pretty useful e.g. if you already had some file that imported all the relevant files and set all the right extensions you could make the literate programming look a lot prettier:
:script setup.ghci
normalize :: DataFrame -> DataFrame
normalize = ...
df <- D.readCsv "./data/test.csv"
print (D.describeColumns (normalize df))