0

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
  • I suspect you're looking for existential types. Commented Dec 14, 2024 at 14:09
  • Why pass foo :: X and use call :: X -> Y -> Z when you could just pass call foo :: Y -> Z in the first place? Commented Dec 14, 2024 at 21:12

3 Answers 3

10

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?

Sign up to request clarification or add additional context in comments.

1 Comment

FWIW Rust does have (nameable) function types for functions or closures that don't capture: fn(A) -> B
3

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.

Comments

1
-- 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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.