I'm playing around with the lambda calculus, but since I'm not too generics-savvy, I would like to confirm my understanding of some things and resolve an error. Consider the following definitions:
pub trait Term {} // a lambda term - a variable, an abstraction or an application
pub struct Var(pub usize); // a variable, De Bruijn style
pub struct Abs<T: Term>(T); // an abstraction
pub struct App<T: Term, U: Term>(T, U); // application of T on U
I understand (i.e. otherwise it doesn't work) that I need App
to be generic over <T: Term, U: Term>
as opposed to just <T: Term>
to be able to e.g. apply a Var
to an App
, i.e. to have an App(Var(x), App(...))
.
The aforementioned structs are all Term
s:
impl Term for Var {}
impl<T: Term> Term for Abs<T> {}
impl<T: Term> Term for App<T, T> {}
Interesting that I don't need App<T, U>
here, but hopefully so far so good - now I would like to implement fmt::Display
for the aforementioned structs:
use std::fmt;
use std::fmt::Display;
impl fmt::Display for Var {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<T: Term+Display> Display for Abs<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "λ{}", self.0)
}
}
These two impl
s work just fine; I'm including them since the next one relies on them:
impl<T: Term+Display> Display for App<T, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
App(Var(_), Var(_)) => write!(f, "{}{}", self.0, self.1),
_ => unimplemented!()
}
}
}
But it fails with:
error[E0308]: mismatched types
--> src\ast.rs:34:8
|
34 | App(Var(_), Var(_)) => write!(f, "{}{}", self.0, self.1),
| ^^^^^^ expected type parameter, found struct `ast::Var`
|
= note: expected type `T`
= note: found type `ast::Var`
I would like to print App
differently based on the types of its contents. I tried to find a related question, but they mostly revolve around associated types. Is there a simple solution or do I have to rework the definitions?
I would like to print App
differently based on the types of its contents.
That's easy enough, just implement Display
for every unique type of App
you wish to be able to print:
use std::fmt;
struct App<T, U>(T, U);
impl fmt::Display for App<i32, bool> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Awesome choice!")
}
}
impl fmt::Display for App<bool, String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Woah, a string? Really?")
}
}
fn main() {
println!("{}", App(42i32, false));
println!("{}", App(true, "wow".to_string()));
}
You can also accept further types that have their own generics:
impl<T> fmt::Display for App<Vec<T>, char> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} of them; {}", self.0.len(), self.1)
}
}
Note that this has no unimplemented!
call; everything is checked at compile time:
error[E0277]: the trait bound `App<bool, bool>: std::fmt::Display` is not satisfied
--> src/main.rs:24:24
|
24 | println!("{}", App(true, false));
| ^^^^^^^^^^^^^^^^ the trait `std::fmt::Display` is not implemented for `App<bool, bool>`
|
= note: `App<bool, bool>` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
= note: required by `std::fmt::Display::fmt`
This has a close analog in the standard library: Cursor
. This type accepts a generic, but all of the interesting functionality is only implemented for a handful of concrete types like &[u8]
or Vec<u8>
.
but it still causes trouble, e.g. with App<Var, App>
, because the inner App
expects 2 type arguments again
Yes, you have to specify the generics because App
isn't a type, it's only a stepping stone towards one.
It all depends on what you want to do. The simplest is to just have an App
composed of two types of any kind, so long as you don't use them:
impl<T, U> fmt::Display for App<i32, App<T, U>> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Semi-awesome")
}
}
If you want to be able to display the App
, then you need to restrict the generics such that App
is displayable:
impl<T, U> fmt::Display for App<i32, App<T, U>>
where App<T, U>: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}) squared!", self.1)
}
}
Check it out with
fn main() {
let a = App(42i32, false);
println!("{}", a);
let b = App(100, a);
println!("{}", b);
let c = App(100, b);
println!("{}", c);
}
I have a guess that the follow up question will be something about having some kind of fallback or default case for all the non-special conditions. Something like:
impl fmt::Display for App<i32, bool> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Awesome choice!")
}
}
impl<T: fmt::Display, U: fmt::Display> fmt::Display for App<T, U> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Boring: {} + {}", self.0, self.1)
}
}
Nope, that won't compile! i32
and bool
also implement Display
so it is ambiguous which implementation to select. At this point, you are into the realm of specialization. This forces you to truly understand the orphan rules.
As far as I understand the current implementation of specialization, you cannot specialize on a concrete type, only on traits.