How to concatenate variable arguments in Haskell?

Viewed 155

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
1 Answers

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.

Related