Note
This project is still work in progress. It has a working proof-of-concept (PoC), that was demonstrated at the MuniHac2024 event.
THSH (Template Haskell SHell) is a "noDSL" approach to mixing shell scripting with Haskell programs using Template Haskell.
The key goal of the project is to enable a gradual approach to adding strongly-typed Haskell code to enhance your shell scripting experience.
It is gradual because you can wrap any shell script into a Template Haskell quasi-quotation with thsh
:
#!/usr/bin/env thsh
__main__ = [|thsh\
echo "Hello Haskell!"
|]
Warning
The current PoC uses the PyF project's internal code to achieve parsing, which presents difficulty in choosing a better Haskell code delimiters other than "« ... »", for now. In the next version, a more palatable syntax should be achieved, e.g. "!{ ... }".
You can pipe the entire outputs from a process to a Haskell function:
#!/usr/bin/env thsh
import Data.Char (toLower)
__main__ = [thsh|\
curl -s https://www.haskell.org/ | «fn (ContentFn $
\content -> "Number of occurrences of the word 'haskell' on haskell.org is "
<> show (length (filter ((== "haskell"). fmap toLower) . words $ content))
<> "\n"
)
»
|]
===>
Number of occurrences of the word 'haskell' on haskell.org is 30
You can also process each line independently:
#!/usr/bin/env thsh
sumSales = LineReadFn
(\ (_ :: String, price, quantity) s -> let s' = s + price * quantity
in (s', Nothing))
(\ s -> Just (show s ++ "\n"))
(0.0 :: Float)
__main__ = [thsh|\
echo -n "Sum of the sales: "; {
# Read (product :: String, price :: Float, quanity :: Float)
«fn sumSales» <<EOF
("apple", 1.2, 100.2)
("orange", 2.0, 34.2)
("pear", 1.2, 62.3)
EOF
} | tail -n1
|]
===>
Sum of the sales: 263.4
Note
More pre-defined function patter will be developed.
The thsh
quoted code can compose with each other:
#!/usr/bin/env thsh
s0 :: Script
s0 = [thsh| bc |]
__main__ = [thsh|\
for i in `seq 0 10`;do
expr="2 ^ $i"
echo -n "$expr = "
echo $expr | «sh s0»
done
|]
|]
-->
= 2
2 ^ 2 = 4
2 ^ 3 = 8
2 ^ 4 = 16
2 ^ 5 = 32
2 ^ 6 = 64
2 ^ 7 = 128
2 ^ 8 = 256
2 ^ 9 = 512
2 ^ 10 = 1024
By default, the "thsh" loader uses the "cabal run" under the hood. That means that you can use cabal specific metadata sections for both package-level and project-level:
{- cabal:
build-depends: diagrams
-}
{- project:
optimization: 2
-}
You can find more examples available under the "examples" folder.
We should note that there have been multiple projects allowing the mixing of Haskell code with shell scripting. All of them require their users to learn an eDSL of their own.
Here is an incomplete list of these projects:
- hell : Haskell-based shell scripting language.
- turtle : turtle is a reimplementation of the Unix command line environment in Haskell so that you can use Haskell as both a shell and a scripting language.
- shelly : shell-like (systems) programming in Haskell.
- shh : Simple shell scripting from Haskell.
- HSH : Library to mix shell scripting with Haskell programs.
By now, it should be evident to you that requiring minimum learning of a new eDSL, aka "noDSL" to be tongue-in-cheek, sets this project apart to offer a viable alternative to Haskell enthusiasts.
Known Bugs and Limitations
- Test with GHC 9.2, 9.4, 9.6, 9.8.
- User pragma fields should be rearranged.
- stdin doesn't work interactively.
- withSystemTempDirectory is not water-proof.
- Better quoting syntax, e.g. "!{ ... }": to replace the
PyF
parser or work with the upstream to reuse.
Features
- THSH script loader, which uses "cabal run" single-script mode.
-
FnFunction
for text package. - IO variants for
FnFunction
instances. - Pass process args to the
__main__
script. -
LineReadFn
for SoP types. - Support
stack
in the loader. - Support
rungc
in the loader. - Support alternative base through a "--alternative-base" option.
Maintainability
- CI system.
- More test cases.
DevX
- Curate a live demo.
- Publish to hackage.
- A "how does it work" section in README.