I got this crazy idea that it would be nice to be able to write something like this:
fn foo(inp: (Option<String>, Option<i32>)) -> (String, i32) {
inp.map_into(|x| x.unwrap())
}
My understanding is that to write map_into
, we would need to extend higher-rank trait bounds to type variables (not just lifetimes):
impl<A, B> (A, B) {
fn map_into<F>(self, f: F) -> (?, ?, ?)
where F: for<T, U> Fn(T) -> U>
{
let (a, b) = self;
(f(a), f(b))
}
}
But even then, I can’t figure out how one would hypothetically write the return type of map_into
. It seems like this is an unfortunate limitation of HRTB. Is there a feature in GHC-flavored Haskell that plays nicely with RankNTypes
that one could look to for inspiration? Is this a legitimate use-case for true return type deduction a la C++14, or C++11-style decltype
? Does it even make sense to use the U
parameter in that way (merely as an “output” type)?
In C++14 with auto
return type and generic lambdas (which I think are higher-rank) one can write this, which more or less does the same thing (ignoring moves/copies):
#include <tuple>
#include <string>
#include <vector>
#include <iostream>
template <typename A, typename B, typename F>
auto map_into(std::tuple<A, B> inp, F f) {
return std::make_tuple(f(std::get<0>(inp)), f(std::get<1>(inp)));
}
int main()
{
auto foo = std::make_tuple(std::string { "foobar" }, std::vector<int> {});
auto bar = map_into(foo, [](auto x) {
// could even be distinct return types, e.g. returning x.cbegin()
return x.size();
});
std::cout << std::get<0>(bar) << " " << std::get<1>(bar) << std::endl;
}