Shell-monad supports variable arguments, however I couldn't find a way to pass a list of such arguments to append. It might be possible to workaround with a function construct present in that library, but I'd like to ask about the general problem.
I have vaguely understood that the "varargs" mechanism is implemented by function composition and recursion is terminated though use of type class inference.
Using that library as an example, I'm wondering if it's possible to treat arguments as "first class" such as assigning two arguments to a variable.
Here's an (incorrect) example that shows my intent.
Prelude Control.Monad.Shell Data.Text> f xs = cmd "cat" xs
Prelude Control.Monad.Shell Data.Text> let a = static ("a" :: Text)
Prelude Control.Monad.Shell Data.Text> let a2 = [a,a]
Prelude Control.Monad.Shell Data.Text> f a2
<interactive>:42:1: error:
• Could not deduce (Param [Term Static Text])
arising from a use of ‘f’
from the context: CmdParams t2
bound by the inferred type of it :: CmdParams t2 => t2
at <interactive>:42:1-4
• In the expression: f a2
In an equation for ‘it’: it = f a2
Nothing a little polymorphic recursion can't fix:
cmdList :: (Param command, Param arg, CmdParams result) =>
command -> [arg] -> result
cmdList command = go . reverse where
go :: (Param arg, CmdParams result) => [arg] -> result
go [] = cmd command
go (arg:args) = go args arg
Try it in ghci:
> Data.Text.Lazy.IO.putStr . script $ cmdList "cat" ["dog", "fish"]
#!/bin/sh
cat dog fish
It requires all the arguments given to cmdList
have the same type, though it does still accept additional arguments of other types not in list form afterwards.
If you're willing to turn on extensions, you can even have it accept lists in multiple positions, each of potentially different types.
list :: (Param arg, CmdParams result) =>
(forall result'. CmdParams result => result') ->
[arg] -> result
list f [] = f
list f (arg:args) = list (f arg) args
An example of using it:
> T.putStr . script $ list (list (cmd "cat") ["dog", "fish"] "bug") ["turtle", "spider"]
#!/bin/sh
cat dog fish bug turtle spider
The previous cmdList
can be defined in terms of it as cmdList command = list (cmd command)
. (N.B. cmdList = list . cmd
does not work!)
Accepting lists that contain different types is noisier, but possible with existential types.
data Exists c where Exists :: c a => a -> Exists c
elist :: CmdParams result =>
(forall result. CmdParams result => result) ->
[Exists Param] -> result
elist f [] = f
elist f (Exists arg:args) = elist (f arg) args
But look how annoying it is to use:
> T.putStr . script $ elist (cmd "cat") [Exists "dog", Exists "fish"]
#!/bin/sh
cat dog fish
The previous list
can be defined in terms of it via list f = elist f . map Exists
.