2

I am trying to find an implementation for this Scala function signature:

def explode[A, B](f: A => List[B]): List[A => B]

The opposite direction is possible:

def nest[A, B](fs: List[A => B]): A => List[B] = (a: A) => fs.map(_(a))

By now I am tending to believe the first one (explode) is unimplementable, but I am happy to be proven wrong. If it is indeed not possible to implement, is there a deep reasoning behind it?

In my view, I am essentially asking the compiler to "duplicate" that input A some n times (the Lists size), and "fix" it as input.

Eliav Lavi
  • 66
  • 6
  • 1
    @LuisMiguelMejíaSuárez See [here](https://stackoverflow.com/questions/65454714/can-a-listb-be-transformed-into-a-lista-b?noredirect=1#comment115732285_65455305). (Also, they're not all the same function, each yields a different `B`) – Eliav Lavi Dec 26 '20 at 21:48
  • Yes, sorry. I still think it would be worth to explain what you want to do – Luis Miguel Mejía Suárez Dec 27 '20 at 00:52
  • Why don't you work with lists of tuples? Or Maps? Something like that: `def explode[A, B](f: A => List[B])(a: A): List[(A, B)] = { f(a).map(b => a -> b) }` ? – Tomer Shetah Dec 27 '20 at 10:12

2 Answers2

4

In my view, I am essentially asking the compiler to "duplicate" that input A some n times (the Lists size), and "fix" it as input.

The problem is you don't know what n is. Consider for example a function that returns a list of all the prime divisors of a number:

def divisors(n: Int): List[Int] = ???

What do you expect explode(divisors) to be? divisors can return a List of any size, depending on its argument, whenever it's invoked in the future. But when you call explode it has to return a List of a fixed size immediately.

Given a fixed type A, the signatures in your code can be written like this:

type F[T] = List[T]
type G[T] = A => T

def nest[B]: F[G[B]] => G[F[B]]
def explode[B]: G[F[B]] => F[G[B]]

nest and explode are reminiscent to sequence operation. It works for nest, because it's possible to write a Traverse instance for a List, but it's not possible to write a Traverse instance for a function A => T. Here is an equivallent question for Haskell, that gives some more insight.

Kolmar
  • 14,086
  • 1
  • 22
  • 25
  • Thank you for your informative response! You write: "...But when you call `nest` it has to return a List of a fixed size immediately." (I believe you meant `explode`). And that is true. I am trying to think if the answer could be different if I was willing to give up the part where the list returns with a fixed size immediately - what if I was to work with `Stream` / `LazyList` etc.? – Eliav Lavi Dec 26 '20 at 12:46
  • 2
    I think, `Stream` is still not good, because it's possible to exhaust it immediately immediately, e.g. `explode(divisors).toList`. And the `List` may have to be empty for some `n`. Is it a theoretical question, or do you have some problem you want to solve? – Kolmar Dec 26 '20 at 13:34
  • I see what you mean. I think empty `List` can work out fine though, but the essence remains in tact. This is mostly theoretical but it stems from a side project I am trying out. I am afraid giving out more detailed context would be too complicated and won't help out. Essentially this means I probably should look up another direction in modeling my problem. – Eliav Lavi Dec 26 '20 at 21:46
2

If you want to make some implementation that just satisfies the signature you can do something like this:

def explode[A, B](f: A => List[B]): List[A => B] = {
  Nil
}

def explode[A, B](f: A => List[B]): List[A => B] = {
  List(f.andThen(_.head))
}

But I guess you want something semantically different:

"duplicate" that input A some n times (the Lists size), and "fix" it as input

In that case, there is a problem. The result of f, in the general case, depends on the input A. It could be Nil, finite-size list, or infinite list.

What you can do is only something like:

def explode[A, B](f: A => List[B]): A => List[A => B] = {
  f.andThen(_.map(b => (_: A) => b)
}
Artem Sokolov
  • 810
  • 4
  • 8