Я пытаюсь найти реализацию этой сигнатуры функции Scala:
def explode[A, B](f: A => List[B]): List[A => B]
Возможно обратное направление:
def nest[A, B](fs: List[A => B]): A => List[B] = (a: A) => fs.map(_(a))
К настоящему времени я склонен полагать, что первый вариант (explode
) невыполним, но я рад оказаться неправым. Если это действительно невозможно реализовать, есть ли за этим глубокая причина?
На мой взгляд, я, по сути, прошу компилятор «продублировать» этот ввод A
несколько n
раз (размер List
s) и «исправить» его как ввод.
Да, прости. Я все же думаю, что стоило бы объяснить, что вы хотите сделать
Почему вы не работаете со списками кортежей? Или карты? Что-то вроде этого: def explode[A, B](f: A => List[B])(a: A): List[(A, B)] = { f(a).map(b => a -> b) }
?
На мой взгляд, я, по сути, прошу компилятор «продублировать» этот ввод
A
несколькоn
раз (размерList
s) и «исправить» его как ввод.
Проблема в том, что вы не знаете, что такое n
. Рассмотрим, например, функцию, которая возвращает список всех простых делителей числа:
def divisors(n: Int): List[Int] = ???
Что вы ожидаете от explode(divisors)
? divisors
может возвращать List
любого размера, в зависимости от его аргумента, всякий раз, когда он вызывается в будущем. Но когда вы вызываете explode
, он должен немедленно вернуть List
фиксированного размера.
Учитывая фиксированный тип A
, подписи в вашем коде могут быть записаны следующим образом:
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
и explode
напоминают последовательность операции. Это работает для nest
, потому что можно написать экземпляр Traverse для List
, но невозможно написать экземпляр Traverse
для функции A => T
. Вот эквивалентный вопрос для Haskell, который дает больше информации.
Спасибо за ваш информативный ответ! Вы пишете: «...Но когда вы вызываете nest
, он должен немедленно вернуть список фиксированного размера». (Я полагаю, вы имели в виду explode
). И это правда. Я пытаюсь подумать, мог ли бы быть другой ответ, если бы я был готов отказаться от части, где список немедленно возвращается с фиксированным размером - что, если бы я должен был работать с Stream
/ LazyList
и т. д.?
Я думаю, Stream
все же нехорошо, потому что его можно исчерпать сразу сразу, например. explode(divisors).toList
. И List
может быть пустым для некоторых n
. Это теоретический вопрос, или у вас есть проблема, которую вы хотите решить?
Я понимаю что ты имеешь ввиду. Я думаю, пустой List
может получиться, но суть остается неизменной. Это в основном теоретически, но это связано с побочным проектом, который я пробую. Боюсь, давать более подробный контекст было бы слишком сложно и не поможет. По сути, это означает, что мне, вероятно, следует поискать другое направление в моделировании моей проблемы.
Если вы хотите сделать какую-то реализацию, которая просто удовлетворяет сигнатуре, вы можете сделать что-то вроде этого:
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))
}
Но я думаю, вы хотите что-то семантически другое:
«продублировать» этот ввод A несколько раз (размер списка) и «исправить» его как ввод
В таком случае есть проблема. Результат f
в общем случае зависит от ввода A
. Это может быть Nil, список конечного размера или бесконечный список.
Что вы можете сделать, это что-то вроде:
def explode[A, B](f: A => List[B]): A => List[A => B] = {
f.andThen(_.map(b => (_: A) => b)
}
@LuisMiguelMejíaSuárez Смотрите здесь. (Кроме того, они не все являются одной и той же функцией, каждая дает разные
B
)