Closed Rua closed 6 years ago
The H
and T
types would have to be specified somehow. What kind of use case do you have in mind?
(P.S. be careful, the term "trait object" is generally understood to refer to dynamic polymorphism! I would say "type parameter")
Hmm... how do you get a trait object like &HList
or Box<HList>
given pub trait HList: Sized { .. }
?
(I'm going to continue assuming the author wasn't talking about trait objects :slightly_smiling_face: )
note: I see mainly two interpretations here, which are:
pub trait TryIntoHCons {
type Head;
type Tail: TryIntoHCons;
fn try_into_hcons(self) -> Option<HCons<Self::Head, Self::Tail>>;
}
impl TryIntoHCons for HNil {
type Head = !;
type Tail = HNil;
fn try_into_hcons(self) -> Option<HCons<!, HNil>> { None }
}
impl<H, T: TryIntoHCons> TryIntoHCons for HCons<H, T> {
type Head = H;
type Tail = T;
fn try_into_hcons(self) -> Option<HCons<H, T>> { Some(self) }
}
which could possibly be used to make some functions on HLists less boiler-platey compared to the status quo (which is that you often need to write to write a trait instead of a function if you want to do structural induction on HCons
vs HNil
). I'm not sure, though; I wonder if the compiler will have difficulty proving that the bounds on such function are well-founded.
edit: ahh, lookitthat, it does work:
fn len<List: TryIntoHCons>(list: List) -> usize {
match list.try_into_hcons() {
None => 0,
Some(HCons(_, tail)) => 1 + len(tail)
}
}
fn main() {
println!("{}", len(HCons((), HCons(1, HNil)))); // 2
}
The other possible interpretation is:
pub trait TryIntoHCons<H, T> {
fn try_into_hcons(self) -> Option<HCons<H, T>>;
}
impl<H, T> TryIntoHCons<H, T> for HCons<H, T> {
fn try_into_hcons(self) -> Option<HCons<H, T>> { Some(self) }
}
impl<H, T> TryIntoHCons<H, T> for HNil {
fn try_into_hcons(self) -> Option<HCons<H, T>> { None }
}
But I don't see what the use cases would be for this in comparison to e.g. pluck
.
Consequently it's not possible to call the HCons-specific methods on it
This part got me thinking about what specific methods there are on HCons
, and I think this is it
2 accessors (head, tail), and a pop. Just thinking aloud; would it perhaps make sense to Option
-returning (for HNil
) functions on Hlist
to satisfy the same usecases as you would if a TryIntHCons
?
There is also get
, pluck
, and into_tuple2
. However:
get
and pluck
I would think that one probably wants to write L: hlist::Selector<T, Index>
or L: hlist::Plucker<T, Index>
as their where
bound rather than L: HList
. (because even if you could obtain an Option<HCons<H, T>>
, you would still need to require that HCons<H, T>: hlist::Selector<T, Index>
or etc. before you could call the method)into_tuple2
could be useful in generic code.@lloydmeta if we had pop
, head
, and tail
methods on HList, where would the Head
and Tail
types come from?
@lloydmeta if we had pop, head, and tail methods on HList, where would the Head and Tail types come from?
Yeah, I clearly didn't think this through enough 😆 They could probably be associated types; maybe for HNil; they would be !
and they would need to return None
s. Again, still going off-the-cuff; might not actually work.
My use case is a function that is part of a trait. This function should receive a HList as an argument, and then call "get" on it to retrieve an item of a particular type, which then gets passed to another function.
trait Foo {
fn call(&self, &HList, Vec<&str>);
}
struct Bar<T>
{
func: Box<Fn(&T, Vec<&str>) + 'static>
}
impl<T> Foo for Bar<T> {
fn call(&self, list: &HList, args: Vec<&str>) {
(self.func)(list.get::<T, _>(), args);
}
}
This code does not currently work because HList has no "get" method. However, if I try to rewrite it so that the "call" function receives HCons<T, Tail>, it doesn't work either, because the T of the list shadows the T of the struct. With HCons<Head, Tail>, I get "the trait frunk::hlist::Selector<T, _>
is not implemented for Tail
". So I'm not sure what the proper solution is in this case.
Oh, you are using a trait object! As @Centril explained, this could never work anyways, because HList: Sized
.
error[E0038]: the trait `frunk_core::hlist::HList` cannot be made into an object
--> src/main.rs:3:1
|
3 | fn func(list: &HList) {}
| ^^^^^^^^^^^^^^^^^^^^^ the trait `frunk_core::hlist::HList` cannot be made into an object
|
= note: the trait cannot require that `Self : Sized`
= note: the trait cannot contain associated consts like `LEN`
I was going to suggest using &hlist::Selector<T, I>
instead, but on further thought that won't work too well because it only works for fixed I
.
Some alternatives I might recommend:
Can you make Foo generic over the list type? If so, you could do something like
trait Foo<L> {
fn func(&self, &L, Vec<&str>);
}
impl<I, L: hlist::Selector<Thing, I>> for Foo<L> { ... }
Alternatively the I
and L
parameters could be on fn func
. ~That would make Foo
an object-safe trait that kinda simulates having a for <L: HList> Fn(&L)
.~ My mistake, generic functions are obviously not object-safe.
Fn
trait object instead of an HList
?I have a HashMap
that stores Foo
s, so it can't have any type parameters or they will no longer fit together into one map.
The func
closure doesn't need a HList, because it only needs a single object of type T. The purpose of the call
function is to find that object among the list given to it, which is why it needs to call .get
. Essentially what is going on is that the HashMap
is storing a bunch of Bar<T>
with different T's (as trait objects), and each of them has to select its own matching T out of a HList.
I've experimented some more and it seems that there is something going on with implementations of Selector
. With the code
impl<T> Foo for Bar<T> {
fn call<Head, Tail>(&self, list: &HCons<Head, Tail>, args: Vec<&str>) {
(self.func)(list.get::<T, _>(), args);
}
}
I get the error "the trait frunk::hlist::Selector<T, _> is not implemented for Tail", as I mentioned before. So I thought, maybe the problem is that it can't figure this out for a generic type T. So I tried:
impl Foo for Bar<i32> {
fn call<Head, Tail>(&self, list: &HCons<Head, Tail>, args: Vec<&str>) {
(self.func)(list.get::<i32, _>(), args);
}
}
so now with a concrete type i32
instead. However I get the same message: "the trait frunk::hlist::Selector<i32, _>
is not implemented for Tail
". When I add an additional requirement, then it works:
impl Foo for Bar<i32> {
fn call<I, Head, Tail: Selector<i32, I>>(&self, list: &HCons<Head, Tail>, args: Vec<&str>) {
(self.func)(list.get::<i32, _>(), args);
}
}
However, this means that the type of item requested is now hardcoded into the trait Foo
which is exactly what I wanted to avoid.
However, this means that the type of item requested is now hardcoded into the trait Foo which is exactly what I wanted to avoid.
Well, it's going to have to be, at least to some extent. What I mean is that, at the very least it will be necessary to encode the full set of possible types T somewhere.
For instance, here's something I tried:
/// `hlist::Selector` with the index type moved to the method
pub trait Has<T> {
fn get_any_index<I>(&self) -> &T
where Self: hlist::Selector<T, I>;
}
impl<Head, Tail, T> Has<T> for HCons<Head, Tail> {
fn get_any_index<I>(&self) -> &T
where Self: hlist::Selector<T, I>,
{ self.get() }
}
// The various "T" types you need
pub struct A(i32);
pub struct B(String);
pub struct C {
bax: String,
buzz: (f64, f64),
}
// ...
pub trait BigHList
: Has<A>
+ Has<B>
+ Has<C>
{ }
trait Foo {
// (unfortunately this doesn't compile because Has is not object-safe)
fn call(&self, list: &BigHList, Vec<&str>);
}
Notice how BigHList
must define all supported T
types. This is absolutely unavoidable; otherwise rust could not know what T
types to use when populating the vtable.
...unfortunately, however, even with that limitation, the above idea does not work, because a generic method like fn get_any_index<I>(&self)
is not object-safe (rust doesn't know what types I
to populate the vtable with). So I'm still not sure how your goal could be accomplished in any fashion.
Predefining the allowed T types is livable, at least, though of course it makes the code less generic. A possible alternative I thought of is to pass a struct containing all the different object types to call
, rather than a HList. Instead, Frunk's generics library is used inside call
to convert it into a HList, from which the appropriate type can then be extracted to pass on to the callback. Would this approach be feasible?
Edit: Come to think of it, could I not just statically define the exact type of list that call
receives using the Hlist!
type macro?
Ah, so it's always the same HList type? Yeah, that should make things much easier.
Yes, you can do something along those lines. Here's a suggestion:
#[macro_use]
extern crate frunk_core;
/// `hlist::Selector` with no mention of index types.
/// (it is only implemented on MyHList)
pub trait FooGet<T> {
fn foo_get(&self) -> &T;
}
// The various "T" types you need
pub struct A(i32);
pub struct B(String);
pub struct C {
pub bax: String,
pub buzz: (f64, f64),
}
// ...
pub type MyHList = Hlist![A, B, C];
// Generate FooGet impls
macro_rules! impl_foo_get {
($($T:ty),+) => {
$(
impl FooGet<$T> for MyHList {
fn foo_get(&self) -> &$T {
self.get()
}
}
)+
}
}
impl_foo_get!{ A, B, C }
pub trait Foo {
fn call(&self, list: &MyHList, strs: Vec<&str>);
}
pub struct Bar<T> {
func: Box<Fn(&T, Vec<&str>) + 'static>
}
// use FooGet as the bound for Bar
impl<T> Foo for Bar<T>
where MyHList: FooGet<T>
{
fn call(&self, list: &MyHList, args: Vec<&str>) {
(self.func)(list.foo_get(), args);
}
}
Closing because I don't think there's anything actionable here for frunk
.
Let us know if you still have trouble.
When a function receives a HList trait object as a parameter, it's not possible to cast it back to a HCons. Consequently it's not possible to call the HCons-specific methods on it. There should be a method that, given a HList, can give you an Option<HCons<H, T>> or similar (with None used when the HList is actually HNil).