3

Ran across this in F# today:

let test<'a> (f: 'a -> unit) =
    let x = f 123
    let y = f "wow"
    ()

Which produces a compiler error, since the generic parameter must be the same within each call of test<'a> as described here: Type inference not working when passing map function

While this makes sense, it made me curious if there are any other languages out there that have implemented something like this--maybe a sort of "parameter-level generics"?

As a follow up, what are the challenges to implementing something like this?

  • 1
    Possible duplicate of [Generic higher order function](https://stackoverflow.com/questions/7213599/generic-higher-order-function) – Phillip Carter Dec 07 '18 at 06:20
  • 2
    I don't think this is a duplicate, especially since that question has only been answered by using workarounds involving inline and interface/class members. I am asking about if there are any languages that implement this sort of thing for first class functions and what challenges a feature like this pose for language development, not why it doesn't work, which I already understood from reading the answer I linked above. –  Dec 07 '18 at 07:02
  • 2
    Instead of a function parameter, you can pass a non-generic interface that has a generic method. – Yacoub Massad Dec 07 '18 at 09:24
  • 1
    @ckemper fair enough! – Phillip Carter Dec 07 '18 at 16:28

2 Answers2

5

Sure. In Haskell, such things are called "higher rank types": they are types in which you are allowed to "move quantification inside parentheses":

test :: (forall a. a -> ()) -> ()
test f = let x = f 123
             y = f "wow"
         in ()

The "challenge" is also described in the link given:

Rank-N type reconstruction is undecidable in general, and some explicit type annotations are required in their presence.

Just rank-2 types are decidable, though, AFAIK (see here). That's why there are two different flags for enabling them in GHC.

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
1

One could also claim C++ supports this with it's weird but quite powerful take on parametric polymorphism.

template<typename T>
void test(T f)
{
  std::cout << f(123)   << std::endl;
  std::cout << f("wow") << std::endl;
}

int main()
{
  test([](auto const & v) 
    { 
      // Note: This is a compile-time test
      if (std::is_same_v<int, std::decay_t<decltype(v)>>)
      {
        return 0;
      }
      else
      {
        return 1;
      }
    });
}