Hacker News new | ask | show | jobs
by kzrdude 1221 days ago
You should look at what you can do with the Rust type inference too, it's not too far away from Haskell, at least not superficially. For example using the return type to deduce type parameters for a method like let samples: Vec<_> = iterator.collect();
1 comments

But this is the most trivial example of inference. Rust has neither generic implementation of higher kinded data nor global inference for the existing specific cases of it like GAT.
Is it the most trivial? C++ doesn't support it, and it has type inference using `auto`.

Note that in my example the type information flows from the variable to picking which generic method that is called, which is reversed information flow of what I would call the most trivial case - when the variable gets its type from the method that was called. (This is trivial: let x = 1i32;)

> For example using the return type to deduce type parameters for a method like let samples: Vec<_> = iterator.collect();

> C++ doesn't support it

How exactly is it different?

    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<int> v1 = {1,2,3};
        auto             it = v1.begin();
        std::vector      v2 (it + 1, it + 2);
    
        for (auto i: v2) { std::cout << i << ' '; }
    }
C++ auto can only do forward type-inference, i.e. the RHS must have a definitive type and this type is what `it` will end up having in your example. In the Rust example from above:

  let samples: Vec<_> = iterator.collect();
The RHS by itself has the type "B for any B that implements FromIterator<I> where I is the Item type of the given iterator". This cannot be assigned directly to a variable without any type hint (like with C++ auto) because it's an entire class of types and not a specific type. However, in the provided example, the Rust type checker can use the provided clue to narrow this down to an exact type, Vec<I>, without requiring an explicit statement of the type I (instead the underscore serves as a placeholder).
Sorry, I still don't get how it's different from C++, and my point wasn't about `auto` but rather about `std::vector v2`:

    std::vector v2 (v1.begin() + 1, v1.begin() + 2);

> However, in the provided example, the Rust type checker can use the provided clue to narrow this down to an exact type

The Rust example is incomplete and its RHS by the time it gets to `.collect()` within the context of `iterator` has to be bound to a particular type of the context via `impl Iterator for <MyContext>`. This is pretty much the same thing as template argument deduction for class templates in C++17 [1][2], and in C++20 it got extended to generic concepts and constraints [3]:

    #include <numeric>
    #include <vector>
    #include <iostream>
    #include <concepts>

    template <typename T> 
    requires std::integral<T> || std::floating_point<T>
    constexpr auto avg(std::vector<T> const &v) {
        auto sum = std::accumulate(v.begin(), v.end(), 0.0);        
        return sum / v.size();
    }

    int main() {
        std::vector v { 1, 2, 3 };
        std::cout << avg(v) << std::endl;                                      
    }
Note that nowhere in the snippet do I specify the type explicitly except for the container of type vector.

[1] https://en.cppreference.com/w/cpp/language/template_argument...

[2] https://devblogs.microsoft.com/cppblog/how-to-use-class-temp...

[3] https://www.cppstories.com/2021/concepts-intro/

I think they meant trivial in comparison to Haskell's type inference, which you had started off comparing against, not C++.