1

How should I type and implement run, so that the following statements work ?

data Run = Run {run :: ??}

f1 = Run (\x -> x)
f2 = Run (\x y-> x+y)
f3 = Run (\x y z -> x*(y+z))

print $ run f1 1 :: Int --> 1
print $ run f2 1 2 :: Int --> 3
print $ run f3 1 2 3 :: Int -> 5

All the polyvariadic functions in Run are of type Int -> ... -> Int: they take a variable number of Int arguments and yield a Int.

If it's any easier, I could live with a solution having a maximum number of arguments, e.g. 3:

data Run
  = Run1 (Int -> Int)
  | Run2 (Int -> Int -> Int)
  | Run3 (Int -> Int -> Int -> Int)

f1 = Run1 (\x -> x)
f2 = Run2 (\x y-> x+y)
f3 = Run3 (\x y z -> x*(y+z))

How would you implement run ?

Pierre Carbonnelle
  • 2,305
  • 19
  • 25
  • What behaviour do you want on `run` being given the wrong number of arguments for a function? Some sort of run-time error? – Probie Jul 04 '18 at 08:56
  • Yes, a run-time error would be fine. In practice, I'll use a recursive type class (i.e. with a base and a recursive definition) to feed it with the number of arguments it needs, e.g. from a random source. – Pierre Carbonnelle Jul 04 '18 at 09:09
  • I suspect [this answer](https://stackoverflow.com/a/48544124/474491) could lead to the solution... – Pierre Carbonnelle Jul 04 '18 at 09:35

1 Answers1

2

Since both f1 and f2 in your code have the same type Run, the type checker can not distinguish between run f1 and run f2 which must have the same type.

This makes it hard to implement variadic functions properly.

It's much easier to use instead

data Run a = Run { run :: a }

so that f1 and f2 no longer share the same type.

If you only care about functions Int -> ... -> Int there might be some solutions using type families, GADTs, DataKinds, and the like. This may be overkill, though, depending on what you are trying to realize.

chi
  • 111,837
  • 3
  • 133
  • 218
  • I'm trying to realize the 3 print statements stated in the question... Do you think you can do it with your suggestion ? – Pierre Carbonnelle Jul 04 '18 at 08:51
  • @user474491 it should work verbatim. Give it a try! – luqui Jul 04 '18 at 09:34
  • Hey, you are right ! It's much simpler than I thought ! Many thanks. – Pierre Carbonnelle Jul 04 '18 at 09:41
  • However, I lose some flexibility in using Run. For example, I cannot have a heterogeneous list of Run, i.e. containing functions having different numbers of parameters. – Pierre Carbonnelle Jul 04 '18 at 10:20
  • @user474491 Correct. The problem is that a list is homogeneous, so in order to put all those functions in the list you need to lose the information about the arity. Then, when you extract an element from the list, this is a "function with unknown arity" so `run (head list)` can not have a precise type. At best, we can have something like `run n (head list)` which will attempt to extract an n-ary function from the list, or fail at runtime if the length is not `n`. – chi Jul 04 '18 at 12:09
  • `data Run a b = Run { run :: a -> b }` might be better, as it at least requires the wrapped value to be a function instead of any arbitrary type. – chepner Jul 04 '18 at 16:33