0

I am attempting to refactor a function (found towards the end of this StackOverflow answer) to make it slightly more generic. Here's the original function definition:

def tryProcessSource(
  file: File,
  parseLine: (Int, String) => Option[List[String]] =
    (index, unparsedLine) => Some(List(unparsedLine)),
  filterLine: (Int, List[String]) => Option[Boolean] =
    (index, parsedValues) => Some(true),
  retainValues: (Int, List[String]) => Option[List[String]] =
    (index, parsedValues) => Some(parsedValues)
): Try[List[List[String]]] = {
  ???
}

And here is to what I am attempting to change it:

def tryProcessSource[A <: Any](
  file: File,
  parseLine: (Int, String) => Option[List[String]] =
    (index, unparsedLine) => Some(List(unparsedLine)),
  filterLine: (Int, List[String]) => Option[Boolean] =
    (index, parsedValues) => Some(true),
  transformLine: (Int, List[String]) => Option[A] =
    (index, parsedValues) => Some(parsedValues)
): Try[List[A]] = {
  ???
}

I keep getting a highlight error in the IntelliJ editor on Some(parsedValues) which says, "Expression of type Some[List[String]] doesn't conform to expected type Option[A]". I've been unable to figure out how to properly modify the function definition to satisfy the required condition; i.e. so the error goes away.

If I change transformLine to this (replace the generic parameter A with Any)...

transformLine: (Int, List[String]) => Option[Any] =
  (index, parsedValues) => Some(parsedValues)

...the error goes away. But, I also lose the strong typing associated with the generic parameter.

Any assistance on with this is very much appreciated.

2
  • Well, how can you convert a List[String] into an arbitrary A? It doesn't really make sense, unless you have some evidence to convert a List[String] to an A already. Some serious refactoring using type-classes could work, but I don't think it makes a lot of sense to have the default parameter anymore. Commented Dec 15, 2015 at 16:30
  • @m-z I now think I understand what you are saying. Please see the answer I just posted and let me know if you seen anything wrong or could be improved. Thank you. Commented Dec 15, 2015 at 19:05

2 Answers 2

1

In transformLine: (Int, List[String]) => Option[A] = (index, parsedValues) => whatever, parsedValues obviously has type List[String], and so Some(parsedValues) is Some[List[String]]. There is simply no reasonable default value for transformLine.

You can change its type to (Int, A) => Option[A] or (Int, List[A]) => Option[List[A]] depending on what you need, and change the previous arguments as well, but you'll get stuck on the fact that lines are Strings, so you'll have to convert them to A somewhere.

Sign up to request clarification or add additional context in comments.

5 Comments

I don't understand your answer. The point of transformLine is to take the index and the parsed string values from a specific line and convert them to an optional arbitrary type. Converting is the whole point of the transformLine function. And since the function can return Option[+Any], why doesn't Some[List[String]] conform to Option[+Any]. IOW, if I change it explicitly to Option[Any] (as opposed to Option[A]), then the error disappears and Some(parsedValue) works fine.
@chaotic3quilibrium - Your default transformLine doesn't transform the line to an arbitrary type, it only works for String, which is why you get the error. An Option[Any] is different to an Option[A] since the client chooses A while the function can easily choose an Any.
@Alexey Romanov Okay, I think I now understand what you are saying. I've now created an answer to my question which more explicitly and concretely expresses what you are implying.
@chaotic3quilibrium Suppose someone calls tryProcessSource[Int](file) (leaving the other arguments as default). Then transformLine needs to have type (Int, List[String]) => Option[Int], which your default value doesn't.
I now think I got it. Please read the answer I just posted to make sure I really did get it. And any feedback you can give me on the guideline/principle I highlighted is greatly appreciated.
0

After reading and re-reading Alexey Romonov's answer and Lee's comment, it gave me a clue into how to refactor this to get it working. Additionally, I think it might also implies a guideline regarding providing default values to a function.

If I am understand it correctly, I am trying to combine a concrete, in this example case List[String] in the transform parameter default, with a generic type, in this case Option[A]. Essentially, this overlaps the boundary upon which a generic copy of the function is instantiated by the compiler for the specific user provided type and the explicitly provided List[String]. This leads me to the following guideline regarding defining generic functions:

When transforming a concrete function to make it generic, each parameter with a default value which interacts directly with any of the generic parameters likely needs to be removed and placed into an enclosing concrete function which forwards the call to the generic function.


So with this guideline in mind, let's rework the original code...

The concrete function looks like this (very similar to the original pre-generic-ified version which also included the desired concrete default parameters):

def tryProcessSource(
  file: File,
  parseLine: (Int, String) => Option[List[String]] =
    (index, unparsedLine) => Some(List(unparsedLine)),
  filter: (Int, List[String]) => Option[Boolean] =
    (index, parsedValues) => Some(true),
  transform: (Int, List[String]) => Option[List[String]] =
    (index, parsedValues) => Some(parsedValues)
): Try[List[List[String]]] =
  tryProcessSourceGeneric(file, parseLine, filter, transform)

And the generic-ified function looks like this (notice the lack of any parameter default values):

def tryProcessSourceGeneric[A, B](
  file: File,
  parseLine: (Int, String) => Option[A],
  filter: (Int, A) => Option[Boolean]
  transform: (Int, A) => Option[B]
): Try[List[B]] = {
  ???
}

Again, a huge thank you to Alexey and Lee for helping me struggle through this.

2 Comments

It can make sense for parameters involving generics to have default values. In your example, you could still have the same default value for filterLine, because it works for any A. Or if you remove the B parameter and have transform: (Int, A) => Option[A], then (_, x) => Some(x) is a reasonable default value.
Awesome! So my guideline holds for filter and my existing transform. However, it doesn't hold for your transform example. Hmmm...not sure how to reword it. Mulling...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.