1

I want a function readFile that takes as input a variable file. File can be both a string or a java.io.File. Suppose to have a function readJavaFile that accept a java.io.File as input.

I want to do something like:

def readFile(file:Either[String, File]) = {
   file match {
     case s:String => readJavaFile(new File(s))
     case s:File => readJavaFile(s)
   }
}

What is the right way to implement this? I saw similar questions on SO but they were referring to more complex situations.

EDIT: I am afraid that Either is not the way to follow. I want to be able to call the function as : readFile(s) where s is a string or readFile(f) where f is a File

EDIT: This is my real code:

def csvread(file: File,
             separator: Char=',',
             quote: Char='"',
             escape: Char='\\',
             skipLines: Int = 0): DenseMatrix[Double] = {
    val input = new FileReader(file)
    var mat = CSVReader.read(input, separator, quote, escape, skipLines)
    mat = mat.takeWhile(line => line.length != 0 && line.head.nonEmpty) // empty lines at the end
    input.close()
    if(mat.length == 0) {
      DenseMatrix.zeros[Double](0,0)
    } else {
      DenseMatrix.tabulate(mat.length,mat.head.length)((i,j)=>mat(i)(j).toDouble)
    }
  }

  def csvread(file: String,
              separator: Char=',',
              quote: Char='"',
              escape: Char='\\',
              skipLines: Int = 0): DenseMatrix[Double] = csvread(new File(file), separator, quote, escape, skipLines)

And I want to call it as:

package breeze.linalg

/**
  * Created by donbeo on 07/02/16.
  */

import org.scalatest._
import org.scalatest.junit._
import org.scalatest.prop._
import org.junit.runner.RunWith
import breeze.linalg.csvread
import java.io.File

@RunWith(classOf[JUnitRunner])
class ReadCsvTest extends FunSuite with Checkers{

  test("Try readcsv") {
    val f = csvread(new File("/Users/donbeo/Documents/datasets/glass.csv"))
    val v = csvread("/Users/donbeo/Documents/datasets/glass.csv")

  }

}

but I receive and error:

Error:(41, 16) in package object linalg, multiple overloaded alternatives of method csvread define default arguments.
package object linalg {
               ^
Donbeo
  • 17,067
  • 37
  • 114
  • 188

3 Answers3

5

Sounds like a perfect case for overloading to me.

 def readFile(s:String) = readJavaFile(new File(s))
 def readFile(f:File) = readJavaFile(f)

Unless you already have the string-or-file in an Either, putting them into an Either just to get them out again seems more complex than is needed.

The Archetypal Paul
  • 41,321
  • 20
  • 104
  • 134
  • this seems the nicest solution. The readJavaFile function takes also other arguments. Is this considered good also in that case? I would have something like: `def readFile(s:String, c:C, d:D)=.. .` and `def readFile(f:File, c:C, d:D)` – Donbeo Feb 07 '16 at 16:01
  • `c` and `d` have default values – Donbeo Feb 07 '16 at 16:09
  • Looks OK to me. How do you want to handle the default values? Do you want readFile to also have default values? – The Archetypal Paul Feb 07 '16 at 16:28
  • yes I have edited the question with part of the real code – Donbeo Feb 07 '16 at 16:34
  • So if you will never need non-default values for the remaining parameters, then the code in my answer will work (with the obvious name changes). If you, then make the cvsread-String-parameter have the same default values, and pass those on to the underlying cvsread-File-parameter. Straightforward, I think? – The Archetypal Paul Feb 07 '16 at 17:23
  • Sorry, read your code now. Yes, I think only one overloaded function can have default arguments. The typechecker can't sort it out otherwise, which is a pity as there's enough information in the type of the first arg to sort it out... Reasons are given here: http://stackoverflow.com/questions/4652095/why-does-the-scala-compiler-disallow-overloaded-methods-with-default-arguments – The Archetypal Paul Feb 07 '16 at 17:55
  • Interestingly, the error seems to go away if you add yet another version of cvsread as "def cvsread(file:String)" - i.e. with no default arguments. Presumably the compiler gets this one first and doesn't have to consider the multiple-default-arguments case. If this works for you it may be a way forward – The Archetypal Paul Feb 07 '16 at 18:03
4

I suppose you want something like this:

  def readFile(file:Either[String, File]) = {
    file match {
      case Left(s) => readJavaFile(new File(s))
      case Right(s) => readJavaFile(s)
    }
  }

Either is a collection of two sets: Left and Right. You can also solve it using fold:

def readFile(file: Either[String, File]): File = 
  readJavaFile(file.fold(new File(_), identity))
Dan Vulpe
  • 1,316
  • 1
  • 11
  • 13
1

What your pattern does, is, match on the type of "file". But that is actualy Either. What you should do instead is match on the kind of Either-instance you have:

file match {
  case Left(s) => readJavaFile(new File(s))
  case Right(s) => readJavaFile(s)
}

What you have would work if your parameter type was Any, but you do not want to do that. (Or the union of File and String, which is something different than Either and something Scala does not have (yet))

dth
  • 2,287
  • 10
  • 17