I have responses from different data sources. Each data source must "promise" to host what I need to build a collection of files. To express this promise, given:
- Response - generic R
- Iterator with Item: File
- Collection
I do not expect the Response to be the Iterator. I expect it to
impl Files {
pub(crate) fn new<R>(kind: Kind, resp: R) -> Files
where
R: IntoIterator,
todo!(),
{
Files {
...
files: todo!() // instantiate using resp
}
}
Given IntoIterator:
pub trait IntoIterator {
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
How do I specify the type Item: File
in the where clause?
(as a bit of an aside, the iterator will use a move semantic)
Something like this should work:
impl Files {
pub(crate) fn new<R>(kind: Kind, resp: R) -> Files
where
R: IntoIterator<Item=File>,
{
Files {
...
files: resp.into_iter().collect() // instantiate using resp
}
}
@2e71828 How about how to express File
as a generic that implements Into<File>
?... I suspect there is no way around a resp
with two generic parameters?
files: resp.into_iter().map(Into::into).collect() // instantiate using resp
Try:
where
R: IntoIterator,
<R as IntoIterator>::Item: Into<File>
1 Like
Wonderful.
I suspect in Haskell the concept is "functional dependency" in a multi-parameter type class. The syntax is something like c -> e
when specifying the type class. It reads "e is uniquely determined by c".
When thinking through the trait spec, I was hanging on the fact that in order to instantiate the File
(item), the compiler won't know the concrete type ?? -> File
. The expression needs to convince the compiler that the input is any one type that implements Into<File>
...?
That’s roughly the role of associated types in Rust: A type can have at most one implementation of IntoIterator
, and each IntoIterator
implementation has exactly one associated Item
type. So, <T as IntoIterator>::Item
necessarily refers to at most one concrete type.
3 Likes
Haskell also has associated type families. Here’s a (rough) equivalent of the traits involved, and ultimately the trait bound in question
{-# LANGUAGE TypeFamilies #-}
import Data.List
-- trait definitions
class Iterator self where
type IteratorItem self
next :: self -> Maybe (IteratorItem self, self)
class
( Iterator (IntoIter self)
, IteratorItem (IntoIter self) ~ IntoIteratorItem self
) => IntoIterator self where
type IntoIter self
type IntoIteratorItem self
intoIter :: self -> IntoIter self
class Into target self where
into :: self -> target
-- unrelated demonstration
collect :: IntoIterator t => t -> [IntoIteratorItem t]
collect = unfoldr next . intoIter
-- mandatory main on godbolt.org
main = return ()
-- main contents
{-
// Rust code for comparison
struct Files;
struct File;
struct Kind;
impl Files {
fn new<R>(kind: Kind, resp: R) -> Files
where
R: IntoIterator,
<R as IntoIterator>::Item: Into<File>,
{
unimplemented!()
}
}
-}
data Files = Files
data File = File
data Kind = Kind
newFiles ::
( IntoIterator r
, Into File (IntoIteratorItem r)
) => Kind -> r -> Files
newFiles = undefined
Compiler Explorer
And here’s the “equivalent” using functional dependencies
{-# LANGUAGE FunctionalDependencies #-}
import Data.List
-- trait definitions
class Iterator item self | self -> item where
next :: self -> Maybe (item, self)
class Iterator item intoIter
=> IntoIterator item intoIter self | self -> item intoIter where
intoIter :: self -> intoIter
class Into target self where
into :: self -> target
-- unrelated demonstration
collect :: IntoIterator item intoIter t => t -> [item]
collect = unfoldr next . intoIter
-- mandatory main on godbolt.org
main = return ()
-- main contents
{-
// Rust code for comparison
struct Files;
struct File;
struct Kind;
impl Files {
fn new<R>(kind: Kind, resp: R) -> Files
where
R: IntoIterator,
<R as IntoIterator>::Item: Into<File>,
{
unimplemented!()
}
}
-}
data Files = Files
data File = File
data Kind = Kind
newFiles ::
( IntoIterator item intoIter r
, Into File item
) => Kind -> r -> Files
newFiles = undefined
Compiler Explorer
1 Like