3
\$\begingroup\$

I recently created Argyle, a small library for parsing command-line arguments in Scala. I'd be happy for anyone to take a look and provide general feedback on it. But there is also one specific issue that sparked some discussion when I posted on reddit, so I would be interested to hear the thoughts of folks here also. Basically, I have the following trait:

trait Arg[+A] {
  def parse(xs: List[String], mode: ArgMode): Try[A]
  def flatMap[B](f: A => Try[B]): Arg[B]
}

The idea is that an Arg[A] handles parsing (and potentially failing to parse) a list of strings into an A. The issue is over the name of the flatMap method. It's a useful method, for instance, converting an Arg[String] to Arg[Int] by .flatMap(s => Try(s.toInt)). But it's not a valid FP Monad flatMap since the parameter type is A => Try[B] and not A => Arg[B].

If you view Arg as a function List[String] => Try[A], then it is actually pretty identical to .andThen(_.flatMap(f)). But I don't know if there's a canonical FP name for an operation like this.

In my mind, pros of naming it flatMap:

  • Similar enough to other flatMap methods that it gives some immediate understanding of how it should function
  • Enables for-comprehension with chains of Trys and such, that work as expected

Cons:

  • Confusing for those who expect flatMap always implies monad, since Arg is not a monad.
  • Potentially confusing that for-comprehensions can't use chains of Args, but that seems nonsensical anyway, and probably shouldn't be allowed.

Any other thoughts, or suggestions for a better name? There is also a map method to deal with.

\$\endgroup\$
1
\$\begingroup\$

I would consider creating an Arrow type from Arg[A] to Arg[B], something like:

import scalaz.Arrow // Also available in Cats    

case class ArgArrow[A, B](run: Arg[A] => Arg[B])

object ArgArrow {
  implicit val arrowInstance: Arrow[ArgArrow] = new Arrow[ArgArrow] {
    def arrTry[A, B](f: A => Try[B]): ArgArrow[A, B] = ???
    def arrOption[A, B](f: A => Option[B]): ArgArrow[A, B] = ???
    // implement other required functions
  }
}

With Arrow, it will be very easy to assemble different things together, for example, you can easily combine an A => B, an A => Try[C], and an A => Option[D] into an ArgArrow[A, (B, C, D)].

\$\endgroup\$
0
\$\begingroup\$

I would be inclined to move the mapping behavior into the Arg for composability

import scala.util.Try
trait ArgMode

trait ArgParse[I, O] {
  def parse(x: I, mode: ArgMode): Try[O]

  final def andThen[B](that: ArgParse[O, B]): ArgParse[I, B] = {
    ArgParse((x, mode) => parse(x, mode).flatMap((that.parse(_, mode))))
  }
}

object ArgParse {

  def apply[I, O](f: (I, ArgMode) => Try[O]) = new ArgParse[I, O] {
    override def parse(x: I, mode: ArgMode): Try[O] = f(x, mode)
  }

  def stringsToInt = ArgParse[List[String], Int]((xs, _) => Try(xs.head.toInt))

  def intToInts = ArgParse[Int, Seq[Int]] ((x, _) => Try(1 to x map (_ => x)))

}    

then compose away

scala> ArgParse.stringsToInt.andThen(ArgParse.intToInts).parse(List("5"), new ArgMode {})
res1: scala.util.Try[Seq[Int]] = Success(Vector(5, 5, 5, 5, 5))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.