In Rust, there is no type for functions, but rather an Fn
trait that looks something like this:
trait Fn<A: Tuple, R> {
fn call(self, args: A) -> R;
}
Then the type of a function can be dyn Fn(A) -> R
. This helps with compiler optimizations while still being pretty simple to understand.
In functional languages, we can similarly define an Fn
type class:
class Fn s a b where
call : s -> a -> b
But, because there is no concept of Self
in type classes, there is no dyn Fn
type, which means you have to use generics for everything:
// in Rust 😊
fn run_with_2(f: &dyn Fn(i32) -> i32) -> i32 {
f.call(2)
}
struct SomeFnWrapper<A,B>(Box<dyn Fn(A) -> B>);
-- in Haskell 🤮
runWith2 : Callable f Int Int => f -> Int
runWith2 f = call f 2
-- creating `SomeFnWrapper` is impossible in Haskell
Is there a solution to this? Perhaps some clever use of forall
I’m not aware of?
2
A solution with existential types which I personally dislike.
class Callable s a b where
call :: s -> a -> b
data SomeCallable a b where
SomeCallable :: forall s . Callable s a b => s -> SomeCallable a b
The reason I dislike this is because there is no real reason for the existential type. Its constructor wraps a value of an unknown callable type s
, on which only call
can be used, so it’s isomorphic to the plain
data SomeCallable a b where
SomeCallable :: (a -> b) -> SomeCallable a b
At that point, you could directly use a -> b
instead. There is nothing to be gained from this complex machinery.
Further, I do not understand the purpose of this. In Rust we have no function types, so using Box<dyn Fn(A) -> B + 'someLifetime>
is a necessity. It’s far more complex than Haskell, but the only sane way to make function types fit the Rust type system. In Haskell, however, we do have function types, so such a workaround is not needed. So… why trying to translate this Rust idiom into Haskell?
1
I think you got your emoji backwards.
// in Rust 🤮
fn run_with_2(f: &dyn Fn(i32) -> i32) -> i32 {
f.call(2)
}
struct SomeFnWrapper<A,B>(Box<dyn Fn(A) -> B>);
-- in Haskell 😊
runWith2 :: (Int -> Int) -> Int
runWith2 f = f 2
type SomeFnWrapper = (->)
In Rust you need a ton of machinery because every different closure has its own different type. In Haskell, the machinery is not needed: a single type, which is also the plain function type, describes all the different closures.
-- why not this?
runWith2 :: (Int -> a) -> a
runWith2 f = f 2
Haskell is a different language than Rust. There is no
need for declaring a typeclass for a function – it is built-in into typechecker. If you still insist on having a specific name for a function, consider type aliases:
type F = forall a. Int -> a
runWith2 :: F -> a
runWith2 f = f 2
Dominik Muc is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.