Sometimes it is nice to have different interpretations of the same data (for example, Point as (x, y) or (dist, angle)). Coming from Rust, there is a standard, almost built-in trait to express that one can perform a conversion from one to the other:
trait From<U> {
fn from(u: U) -> Self;
}
trait Into<T> {
fn into(self: Self) -> T;
}
(normally you define From
instances, and Into
is provided for free).
Their docs are From
and Into
.
I suppose the equivalent Scala trait would be
trait From[U, T]:
def from(u: U): T
This is fairly useful in Rust, especially in argument-position, like so:
fn norm(intoPt: impl Into<Point>) -> f64 {
val pt = intoPt.into();
(pt.x * pt.x + pt.y * pt.y).sqrt()
}
The above function is implicitly generic on the type of pt
, effectively creating a hidden type variable with a context bound Into<Point>
. Once inside the function, we can just call the .into()
method to convert the argument into type Point
.
This is not as convenient as having implicit parameters on the implementor’s side, but is just as convenient on the user’s.
Some interesting built-in trait implementations are:
From<T> for T
(obviously identity)From<T> for Option<T>
(makes optional parameters nicer!)
In Scala, I found the Conversion trait to be really close to From
/Into
, but it seems to be more of a implicit conversion thing than explicit conversions.
Furthermore, the obvious instance of
inline given[T]: Conversion[T, T] = identity
is somehow missing, making using it quite… annoying to use.
For reference, norm
can be implemented in Scala with Conversion
like so:
type Into[T] = [U] =>> Conversion[U, T]
def norm[T: Into[Point]](intoPt: T): Double =
val pt = intoPt.convert
(pt.x * pt.x + pt.y + pt.y).sqrt
Since Conversion
doesn’t seem to be widely used, I wanted to ask that:
- Does explicit conversions come up often in your code? and if so, …
- How do you manage them? Through your own trait, with
.asX
methods, or something else?