@hackage THSH0.0.0.4

A "noDSL" approach to mixing shell scripting with Haskell programs using Template Haskell

[!NOTE]

This project is still work in progress. It has a working proof-of-concept (PoC), that was demonstrated at the MuniHac2024 event.

THSH

THSH (Template Haskell SHell) is a "noDSL" approach to mixing shell scripting with Haskell programs using Template Haskell.

Key Features

Gradual "Haskellization"

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!"
|]

Pipe To Haskell Functions

[!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.

Composable

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

Cabal Single-Script Metadata

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
-}

More Examples

You can find more examples available under the "examples" folder.

Comparing To eDSL Solutions

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:

  1. hell : Haskell-based shell scripting language.
  2. 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.
  3. shelly : shell-like (systems) programming in Haskell.
  4. shh : Simple shell scripting from Haskell.
  5. 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.

TODOs For The Beta (0.0.1.0) Release

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.
  • 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.