Name-Based Implicit Parameters
Some functions need additional parameters that can be easily inferred based on their name and type. For example, a sort function might want to get a order as its argument:
def mySort1[A](x: List[A]){ compare: (A, A) => Ordering }: List[A] =
// we use the standard library sort here, which gets a less than or equal
x.sortBy{ (x, y) =>
compare(x,y) match {
case Greater() => false
case _ => true
}
}
def compare(x: Int, y: Int): Ordering = compareInt(x, y)
mySort1([3,1,2]){ (x, y) => compare(x, y) }
However, often there will be a function compare in scope, and we could
simplify the call if this was used automatically. In Effekt, we can
instruct the compiler to generate the block parameter like above by
putting a ? in front of the parameter:
def mySort2[A](x: List[A]){ ?compare: (A, A) => Ordering }: List[A] =
// we use the standard library sort here, which gets a less than or equal
x.sortBy{ (x, y) =>
compare(x,y) match {
case Greater() => false
case _ => true
}
}
Then we can just call it as:
mySort2([3,1,2])
This generates something like the following, which we are still allowed to write explicitly, too:
mySort2([3,1,2]){ (x, y) => compare(x, y) }
Name-based implicit parameters also will be passed to the functions that get called implicitly like this, so if we define
def compare[A](x: Option[A], y: Option[A]){ ?compare: (A,A) => Ordering }: Ordering =
(x, y) match {
case (None(), None()) => Equal()
case (Some(_), None()) => Greater()
case (None(), Some(_)) => Less()
case (Some(x), Some(y)) => compare(x, y)
}
we can now also call mySort2 with lists of options (or options of options, …):
mySort2([Some(12), None(), Some(2)])
This will expand to code equivalent to:
mySort2([Some(12), None(), Some(2)]){ (x,y) => compare(x, y){ (u, v) => compare(u, v) } }
Values
We can also pass value parameters implicitly when they are marked with a ?:
def foo(?context: String): Unit =
println(s"Called from ${context}")
def example1() = {
val context = "example1"
foo()
}
def example2(context: String) = {
example1()
foo()
}
example2("ex2")
That is, implicit value parameters use any value binding of the correct name at the call site. For some special cases, they work differently, though. We will discuss those now.
Boxing
If the value parameter has a boxed type (something like A => B at {}),
this is expanded to box {...} and expanded like block parameters, so
the following works:
def mySort3[A](x: List[A], ?compare: (A, A) => Ordering at {}): List[A] =
mySort2(x){ (x,y) => (unbox compare)(x, y) }
mySort3([12,3,42])
Source Positions
When a function takes an implicit parameter with the name sourcePosition,
it will get the result of calling SourcePosition with the filename,
start line, start column, end line, and end column of the source position of the call:
def printSourcePos(?sourcePosition: SourcePosition): Unit =
println(sourcePosition.show)
printSourcePos()
Call IDs
Sometimes we would use source positions just to get a unique id for each
call in the source code. Instead, we can take a parameter callId: Int
which will get a unique integer ID directly:
def here(?callId: Int): Int = callId
here() == here()