6

So let's say I want to create a custom function type called ImportFunc that takes in an Int called fileImportID and a string called filename. I can do this pretty easily using a type alias like so:

type ImportFunc = (Int, String) => Unit

The problem is, anybody trying to use this function has no idea what Int and String are actually supposed to be. Is there some way I can write something like:

type ImportFunc = (fileImportID: Int, filename: String) => Unit

4 Answers 4

5

When you call a function, you actually call the function's apply method. In other words, given this:

def doImport(fileImportID: Int, filename: String) {
  println(s"Importing file #$fileImportID ($filename)")
}

The following snippet:

val f = doImport _
f(123, "file.txt")

...is just syntactic sugar for:

val f = doImport _
f.apply(123, "file.txt")

If there is a place where the compiler will look for the arguments's names when doing a call with named parameters, that's necessarily in the apply method's definition. It turns out that in Function2, those arguments are named v1 and v2. So we can do:

scala> f.apply(v1=123, v2="file.txt")
Importing file #123 (file.txt)

Now let's see if it still works when using the syntactic sugar (in other words when removing the explicit call to apply):

scala> f(v1=123, v2="file.txt")
Importing file #123 (file.txt)

Nice, it works. Now of course v1 and v2 is not quite the same as fileImportID and filename, but we can fix that with a bit of type refinement:

type ImportFunc = ((Int, String)=>Unit) { 
  def apply(fileImportID: Int, filename: String): Unit 
}

Basically this is just (Int, String)=>Unit (or in other words Function2[Int, String, Unit]) but with a redefinition of apply with our desired argument names. Let's see this in action:

scala> val f: ImportFunc = doImport _
f: ImportFunc = <function2>
scala> f(fileImportID=123, filename="file.txt")
Importing file #123 (file.txt)

Success!

An important side note: in terms of typing, ImportFunc is identical to Function2[Int, String, Unit], or to any other similar refinement. This is because argument names are not part of the signature. So in my example f can still be passed anywhere a Function2[Int, String, Unit] is expected (but from that point you won't be able anymore to call it using your custom argument names).

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

Comments

3

In Scala, functions are defined from traits FunctionX, so you can do as following:

trait ImportFunc extends ((Int, String) => Unit) {
  def apply(fileImportId: Int, filename: String): Unit
}

// Then custom definition can be implemented as following
val f1: ImportFunc = new ImportFunc {
  def apply(fid: Int, fn: String): Unit = ???
}
f1(1, "name") // call it

/** Companion object to ease the use */
object ImportFunc {
  /** Function factory: take a plain (Int, String) => Unit 
    and turn it into documented type */
  def apply(f: (Int, String) => Unit): ImportFunc = new ImportFunc {
    def apply(fileImportId: Int, filename: String): Unit = f(fileImportId, filename)
  }
}

val f2: ImportFunc = ImportFunc((fid: Int, fn: String) => ???)
f2(2, "eman") // call it

Comments

2

A simple "type" solution:

type FileImportID = Int
type Filename = String
type ImportFunc = (FileImportID, Filename) => Unit

4 Comments

Not fond of case class to wrap value for naming purpose
I moved this to a another answer.
I'm not sure why you moved it to another answer, other than to possibly try to get more rep. Why not just keep both options in the same one since they are so similar.
@kingdamian42: I just think they are different answers. I personally like the other one better, but this makes sense when you don't like to type everything like I do.
-1

I'm not too fond of Int and String as they are to easy to mix up with other Strings and Ints. Do:

case class FileImportID(value: Int) extends AnyVal
case class Filename(value: String) extends AnyVal

//Leading to 
type ImportFunc = (FileImportID, Filename) => Unit

2 Comments

I use types for almost everything. I tend to mix up Strings, Ints and the standard AnyVals in any function call where there are more than two parameters that are the same. So no Strings and Ints in the wild.

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.