Reputation: 95
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
Upvotes: 6
Views: 1396
Reputation: 32719
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).
Upvotes: 5
Reputation: 515
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
Upvotes: -1
Reputation: 515
A simple "type" solution:
type FileImportID = Int
type Filename = String
type ImportFunc = (FileImportID, Filename) => Unit
Upvotes: 2
Reputation: 9168
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
Upvotes: 3