Некоторое время назад был задан вопрос и дан ответ < / a> в списке рассылки Scala:
Кевин:
Учитывая некоторую вложенную структуру:
List[List[...List[T]]]
какой лучший (желательно безопасный по типу) способ свести его кList[T]
Джеспер:
Комбинация имплицитов и аргументов по умолчанию работает:
case class Flat[T, U](fn : T => List[U])
implicit def recFlattenFn[T, U](implicit f : Flat[T, U] = Flat((l : T)
=> List(l))) =
Flat((l : List[T]) => l.flatMap(f.fn))
def recFlatten[T, U](l : List[T])(implicit f : Flat[List[T], U]) = f.fn(l)
Примеры:
scala> recFlatten(List(1, 2, 3))
res0: List[Int] = List(1, 2, 3)
scala> recFlatten(List(List(1, 2, 3), List(4, 5)))
res1: List[Int] = List(1, 2, 3, 4, 5)
scala> recFlatten(List(List(List(1, 2, 3), List(4, 5)), List(List(6, 7))))
res2: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
Я давно смотрю на этот код. Я не могу понять, как это работает. Кажется, здесь есть какая-то рекурсия ... Кто-нибудь может пролить свет? Есть ли другие примеры этого паттерна и есть ли у него название?
2 ответа
Ого, это старый! Я начну с того, что немного поправлю код и приведу его в соответствие с текущими идиоматическими соглашениями:
case class Flat[T, U](fn: T => List[U])
implicit def recFlattenFn[T, U](
implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
) = Flat((xs: List[T]) => xs flatMap f.fn)
def recFlatten[T, U](xs: List[T3])(implicit f: Flat[List[T], U]) = f fn xs
Затем, без лишних слов, разбейте код. Во-первых, у нас есть класс Flat
:
case class Flat[T, U](fn: T => List[U])
Это не что иное, как именованная оболочка для функции T => List[U]
, функции, которая будет строить List[U]
при наличии экземпляра типа T
. Обратите внимание, что T
здесь также может быть List[U]
, или U
, или List[List[List[U]]]
и т. Д. Обычно такая функция может быть напрямую указана как тип параметр. Но мы собираемся использовать его в неявных выражениях, поэтому именованная оболочка избегает любого риска неявного конфликта.
Затем, работая в обратном направлении от recFlatten
:
def recFlatten[T, U](xs: List[T])(implicit f: Flat[List[T], U]) = f fn xs
Этот метод примет xs
(List[T]
) и преобразует его в U
. Для этого он находит неявный экземпляр Flat[T,U]
и вызывает вложенную функцию fn
Затем настоящая магия:
implicit def recFlattenFn[T, U](
implicit f: Flat[T, U] = Flat((xs: T) => List(xs))
) = Flat((xs: List[T]) => xs flatMap f.fn)
Это удовлетворяет неявному параметру, требуемому recFlatten
, но также принимает другой неявный параметр. Самое главное:
recFlattenFn
может действовать как собственный неявный параметр- он возвращает Flat [List [X], X], поэтому
recFlattenFn
будет неявно разрешен какFlat[T,U]
, только еслиT
являетсяList
- неявное
f
может вернуться к значению по умолчанию, если неявное разрешение не удается (т. е.T
НЕ являетсяList
)
Возможно, лучше всего это понять в контексте одного из примеров:
recFlatten(List(List(1, 2, 3), List(4, 5)))
- Тип
T
выводится какList[List[Int]]
- неявный поиск выполняется для `Flat [List [List [Int]], U]
- этому соответствует рекурсивно определенный
recFlattenFn
Говоря в широком смысле:
recFlattenFn[List[List[Int]], U] ( f =
recFlattenFn[List[Int], U] ( f =
Flat[Int,U]((xs: T) => List(xs)) //default value
)
)
Обратите внимание, что recFlattenFn
будет соответствовать только неявному поиску Flat[List[X], X]
, а параметры типа [Int,_]
не соответствуют этому совпадению, потому что Int
не является List
. Это то, что вызывает возврат к значению по умолчанию.
Вывод типа также работает в обратном направлении вверх по этой структуре, разрешая параметр U
на каждом уровне рекурсии:
recFlattenFn[List[List[Int]], Int] ( f =
recFlattenFn[List[Int], Int] ( f =
Flat[Int,Int]((xs: T) => List(xs)) //default value
)
)
Это просто вложение экземпляров Flat
, каждый из которых (кроме самого внутреннего) выполняет операцию flatMap
для развертывания одного уровня вложенной структуры List
. Самый внутренний Flat
просто объединяет все отдельные элементы в один List
.
Что и требовалось доказать
Хорошим решением может быть попытка посмотреть, как вводятся типы. Чтобы избежать двусмысленности, переименуем дженерики:
case class Flat[T, U](fn : T => List[U])
implicit def recFlattenFn[T2, U2](implicit f : Flat[T2, U2] =
Flat((l : T2) => List(l))) =
Flat((l : List[T2]) => l.flatMap(f.fn))
def recFlatten[T3, U3](l : List[T3])(implicit f : Flat[List[T3], U3]) = f.fn(l)
В первом случае, res0
, типом T3
является Int
, вы пока не можете сделать вывод о типе U3
, но вы знаете, что вам понадобится Flat[List[Int, U3]]
объект, который будет предоставлен неявно. Есть только один «неявный кандидат»: результат функции recFlattenFn
и его тип Flat[List[T2], List[U2]]
. Таким образом, T2
= Int
и U2
= U3
(что нам еще нужно вывести).
Теперь, если мы хотим иметь возможность использовать recFlatten
, мы должны предоставить ему параметр f
. Вот уловка . Вы можете использовать неявный тип Flat[Int, U2]
или значение по умолчанию типа Int => List[Int]
. Давайте посмотрим на возможные имплициты. Как объяснялось ранее, recFlattenFn
может предоставить объект Flat[List[T2], U2]
(для новых T2
и U2
) объекта. На данный момент это не соответствует ожидаемой сигнатуре f
. Таким образом, никакие неявные варианты здесь не подходят, и мы должны использовать аргумент по умолчанию. Поскольку тип аргумента по умолчанию - Int => List [Int], U2
и U3
равны Int
, и мы идем.
Надеюсь, эта длинная проза поможет. Я оставляю вас с разрешением res1
и res2
.
Похожие вопросы
Связанные вопросы
Новые вопросы
scala
Scala - это язык программирования общего назначения, в основном предназначенный для виртуальной машины Java. Разработанный для краткого, элегантного и безопасного для типов представления общих шаблонов программирования, он сочетает в себе как императивный, так и функциональный стили программирования. Его ключевые особенности: продвинутая система статического типа с выводом типа; типы функций; сопоставления с образцом ; неявные параметры и преобразования; перегрузка оператора; полная совместимость с Java; совпадение