5

Playing around with the ReadArgs package, it seems that it does not support single-argument situations.

{-# LANGUAGE ScopedTypeVariables #-}

import ReadArgs (readArgs)

main = do
  (foo :: Int) <- readArgs
  print foo

The error is (when using version 1.0):

No instance for (ReadArgs.ArgumentTuple Int)
  arising from a use of `readArgs'

My question is twofold:

  1. How does readArgs work?
  2. How can that library be adjusted to allow it to work with a single argument as well?

N.B. version 1.1 of ReadArgs eliminates this "error"; see comments.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • It sounds like readArgs expects to provide a tuple. I don't think you can have a tuple of *one* item--`(1) is just 1`--so that's probably the issue. – Tikhon Jelvis Jan 06 '12 at 02:15
  • @TikhonJelvis yes, that is indeed the issue. But I still want to know how it works and how it could be adjusted to allow for non-tuple situations. – Dan Burton Jan 06 '12 at 02:18
  • 1
    Probably the right solution is to write an instance for [OneTuple](http://hackage.haskell.org/packages/archive/OneTuple/0.2.1/doc/html/Data-Tuple-OneTuple.html)s. – Daniel Wagner Jan 06 '12 at 02:36
  • What would you like to know about how it works? – rampion Jan 06 '12 at 14:49
  • @rampion anything and everything. :) hammar's explanation was satisfactory for me but deeper explanations are also welcome. – Dan Burton Jan 06 '12 at 15:31
  • Well, it started as [me wondering how to parse command line arguments](http://stackoverflow.com/questions/8610277/haskell-parsing-command-line-arguments), then [running it past reddit](http://www.reddit.com/r/haskell/comments/nny43/readargs_for_quick_argument_parsing/), who gave me some pointers, before I decided to package it up. The real trick was figuring out what `LANGUAGE` pragma I needed and why. I wanted to be able to parse all `Read`able types, but handle `String` and `Char` specially (so quoting wouldn't be required). – rampion Jan 06 '12 at 18:28
  • What it really made me realize was that we could use types as a regular language ~ tuples for concatenation, () for empty, Either for alternation, [] for Kleene star. Then the type signature becomes a regular expression which is used to parse the arguments. – rampion Jan 06 '12 at 18:36

4 Answers4

9

From what I can tell, the package uses tuples to emulate type-safe heterogeneous lists. As you noticed, this causes problems when you want only one argument, as there are no one-tuples in Haskell.

However, the package also provides a proper type for heterogeneous lists, which can be used instead of tuples: the :& type. You use it similar to the : operator, with the empty tuple () as a terminator:

(foo :: Int) :& (bar :: String) :& () <- readArgs

This works trivially with one argument:

(foo :: Int) :& () <- readArgs
hammar
  • 138,522
  • 17
  • 304
  • 385
3

Another answer, just for fun: reifying Daniel Wagner's suggestion of adding an instance for OneTuple:

{-# LANGUAGE ScopedTypeVariables #-}

import ReadArgs
import Data.Tuple.OneTuple

instance (Argument a) => ArgumentTuple (OneTuple a) where
  parseArgsFrom ss = do
    a :& () <- parseArgsFrom ss
    return $ OneTuple a
  usageFor (OneTuple a) = usageFor (a :& ())

main = do
    OneTuple (foo :: Int) <- readArgs
    print foo

Mostly stolen from Adam Wagner's solution. Amazingly, all the extra language pragmas can be removed.

This may sound silly, but the OneTuple (foo :: Int) <- readArgs isn't nearly as ugly as I imagined it would be for some reason.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • +1 for getting rid of the language pragmas. Aside from that, is there a good semantic reason to use `OneTuple`? I'm not sure I have an opinion on the matter, I'm just curious. – Adam Wagner Jan 06 '12 at 06:32
  • Why is the `ScopedTypeVariables` pragma required? – is7s Jan 06 '12 at 11:11
  • @is7s `do OneTuple (foo :: Int) <- readArgs; ...` is sugar for `readArgs >>= (\(OneTuple (Foo :: Int)) -> ...)`, which apparently requires `ScopedTypeVariables` for that type annotation. I'm not sure why, but GHC yells at me if I don't put it in, and suggests "Use -XScopedTypeVariables" – Dan Burton Jan 06 '12 at 15:29
  • @Dan Thanks. Apparently that pragma is required in any type declarations when pattern-matching. For example in `[1,2] >>= \(x::Int) -> [x+1]`. – is7s Jan 06 '12 at 21:06
2

I don't quite understand all of the extensions I needed to enable, but you could define an instance of ReadArgs.ArgumentTuple a (even though it's not really a semantically correct name) like this:

{-# LANGUAGE FlexibleInstances, UndecidableInstances,
             OverlappingInstances, ScopedTypeVariables #-}

import ReadArgs

instance (Argument a) => ArgumentTuple a where
  parseArgsFrom ss = do
    a :& () <- parseArgsFrom ss
    return a
  usageFor a = usageFor (a :& ())

main = do
    (foo :: Int) <- readArgs
    print foo

Also, I'm not really sure if there are any problems with this instance, though it works for the example you presented. I would assume there's a reason it's missing from the library, but I may be wrong.

Update

After looking trying a few things out, to be sure they still work (like the following example), I'm fairly convinced this doesn't cause any problems, so maybe it's (or something similar) existance was just an oversight.

main = do
    (foo :: Int, bar :: Int) <- readArgs
    print foo
    print bar
Adam Wagner
  • 15,469
  • 7
  • 52
  • 66
  • `TypeSynonymInstances` and `TypeOperators` are not necessary for your code snippet, although they *are* used in ReadArgs.hs – Dan Burton Jan 06 '12 at 05:19
  • I've updated my example... thanks! That is where I got them from, as you probably noticed. – Adam Wagner Jan 06 '12 at 05:22
  • I'm accepting this answer, as it provides the exact syntax that I asked for. :) – Dan Burton Jan 06 '12 at 05:36
  • I think at some point, I'd tried something like this with and run into an overlapping instances problem, but you seem to have proved it works. If you send me a [pull request](https://github.com/rampion/ReadArgs), I'd be happy to integrate it into the next package version. – rampion Jan 06 '12 at 14:43
  • 1
    @DanBurton: merged, packaged, and [released as version 1.1](http://hackage.haskell.org/package/ReadArgs) – rampion Jan 06 '12 at 18:07
  • @DanBurton You beat me to it! Oh well, at least I was able to help. :) – Adam Wagner Jan 06 '12 at 20:36
  • @AdamWagner sorry I couldn't resist xD My git-fu is weak, so I wanted some practice. My first pull request ever! :) – Dan Burton Jan 06 '12 at 21:44
  • @DanBurton No worries.. thanks for giving me props in the pull request. – Adam Wagner Jan 06 '12 at 23:53
1

One retarded solution is to abuse the "optional argument" feature:

(foo :: Int, _ :: Maybe ()) <- readArgs

This silently works even if you supply () as the second arg:

$ runhaskell args.hs 3 ()
3

And screws up the usage message a little:

$ runhaskell args.hs 3 foo
usage: args.hs Int [()]

But it does reject extra arguments that are not (), and it does work as desired:

$ runhaskell args.hs 3
3
Dan Burton
  • 53,238
  • 27
  • 117
  • 198