Juan-Carlos Gandhi (
juan_gandhi) wrote2013-01-23 07:37 pm
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Entry tags:
and more on type classes in Scala
This part is not exactly a theory of algebraic theories, but anyway.
Imagine we have a parametric class (e.g.
Suppose we want to declare
The trick would be to have an implicit transformation handy that transforms
Where can we find such a general transformation? The only thing that comes up to mind is an identity:
This would be enough in this specific case... but then we would have to define another one for numeric types, and so on.
We can go generic, and write something like this:
Now we can define
All the conditions are satisfied now. Contravariance in the first argument and covariance in the second argument ensure that a subtype can be cast to its supertype... to be more precise, an instance of supertype can be substituted by an instance of subtype.
The only thing is that we can try to make the code more readable, by refactoring.
Step 1. Use the trick in Scala that binary operations can be written in an infix way, even for types. E.g. you can declare
Step 2. Rename the method, giving it a more "type-relationship" name:
That's the trick.
(if you have questions, ask; if you have corrections, please tell me)
Imagine we have a parametric class (e.g.
List[X]
) for which we would like to define methods that would only work if X
satisfies certain conditions. E.g. define sum
if it is a numeric type, or define flatten
if it is some kind of sequence (Traversable[Y]
would be enough). How can we accomplish it in Scala?!Suppose we want to declare
flatten
trait List[X] { // bla-bla-bla def flatten[X <% Iterable[Y]] { ... } // no way, we already mentioned X; this one would shadow the previous one // OOPS! }
The trick would be to have an implicit transformation handy that transforms
X
into Iterable[Y]
for some Y
; and define it so that if X
is actually some kind of Iterable
, the implicit would be available, and otherwise not available.Where can we find such a general transformation? The only thing that comes up to mind is an identity:
implicit def itsme[Y, X <% Iterable[Y]](x: X): Iterable[Y] = x
This would be enough in this specific case... but then we would have to define another one for numeric types, and so on.
We can go generic, and write something like this:
abstract class SafeToCast[-S, +T] extends Function1[S, T] implicit def canCast[X <% Y, Y]: SafeToCast[X, Y] = new SafeToCast[X,Y] { def apply(x:X) = x }
Now we can define
flatten
, like this:class List[X] { ... def flatten[Y](implicit transformer: SafeToCast[X, Iterable[Y]]) { ... } }
All the conditions are satisfied now. Contravariance in the first argument and covariance in the second argument ensure that a subtype can be cast to its supertype... to be more precise, an instance of supertype can be substituted by an instance of subtype.
The only thing is that we can try to make the code more readable, by refactoring.
Step 1. Use the trick in Scala that binary operations can be written in an infix way, even for types. E.g. you can declare
val map: (String Map Int)
- this is the same as Map[String, Int]
.sealed abstract class SafeToCast[-S, +T] extends Function1[S, T] implicit def canCast[X <% Y, Y]: (X SafeToCast Y) = new (X SafeToCast Y) { def apply(x: X) = a } ... class List[X] { ... def flatten[Y](implicit transformer: X SafeToCast Iterable[Y]) { ... } }
Step 2. Rename the method, giving it a more "type-relationship" name:
SafeToCast
-> <:<
.sealed abstract class <:<[-S, +T] extends Function1[S, T] implicit def canCast[X <% Y, Y]: (X <:< Y) = new (X <:< Y) { def apply(x: X) = a } ... class List[X] { ... def flatten[Y](implicit transformer: X <:< Iterable[Y]) { ... } }
That's the trick.
(if you have questions, ask; if you have corrections, please tell me)
no subject
e.g. List[String].sum не откомпилируется.
no subject
Не то, чтобы я не видел подобного в реале, но просто интересны предположения тех, кто такое позволяет.
no subject
no subject
no subject
С другой стороны, более продвинутые программисты, которые в курсе имплиситов и их применимости для обсуждаемых задач, будут в курсе и насчет кастомных сообщений об ошибках, так что их это в заблуждение не введет. На крайний случай есть -Xlog-implicits.
Хочу отдельно сконцентрировать внимание на то, что имплиситы, обсуждаемые здесь, нельзя случайно забыть импортировать - они всегда в скоупе при помощи механизмов, описанных в гугле по ключевым словам implicits without import tax. Поэтому тут либо коллекция поддерживает сумму по элементам, либо не поддерживает и ее надо допиливать, что явно за пределами возможностей обычных пользователей.
no subject
Оп-с. С каких времён мир разделился на "обычных пользователей" и "продвинутых программистов"? Практика показывает, что предполагать наличие каких-то особых качеств у людей - это очень опрометчиво.
что явно за пределами возможностей обычных пользователей
Ха.
Сложно - это правильно расширить, сохранив логику и ясность. "Обычные пользователи" допилят как смогут.
Стандартная ситуация: сделать надо было вчера и есть крутой алгоритм, найденный гуглом. Скопировали, поменяли что надо, а оно не компилируется. Дальше начинается индийские танцы. Потом придётся удивляться результатам сложения элементов списка, содержащего числа, даты и строки.
no subject
no subject
no subject
no subject
no subject
no subject
Сишарп я бы за пример не брал.
no subject
Вот как, например, было в посте:
implicit def canCast[X]: SafeToCast[X, X] = new SafeToCast[A,A] { def apply(a: A) = a }
Здесь implicit определение выражает то, что можно скастовать любой тип сам в себя. Никто никого не преобразовывает, просто на основе фактов, указанных таким образом, тайпчекер выдает ответ: "да, подходит" или "нет, не подходит".
no subject
В хаскелле экзистенциальные типы и rank-N полиморфизм выражаются с помощью (forall blah . P blah => ...), что на деле выражается в виде функции с неявным параметром, который в точке вызова конструируется неявно же, и является ничем иным как словарём функций.
В скале, собственно, происходит то же самое - неявный параметр, в точке вызова конструируется неявно, и является ничем иным как словарь функций = object. Под таким углом как-то виднее, почему такое ударение на имплиситы по сравнению с экзистенциальными констрейнтами.
С одной стороны, какой-то сумбур, путаница с иерархией классов дополнительно к путанице с имплиситами. С другой стороны, как раз решение проблемы неявной передачи словаря, которую в хаскеле решить ...скажем, не так удобно. (Как сообщить, какой именно моноид на Int? в скале - либо локально импортируй один из моноидов, либо передавай явно)
ага, а чё я, собственно, это всё говорю. Если имплиситы всё более дурным тоном считается, то что ему на замену хорошим тоном считают?
no subject
no subject
(no subject)
(no subject)
(no subject)
no subject
no subject
[1] http://yz.mit.edu/wp/true-scala-complexity/
[2] http://news.ycombinator.com/item?id=3443436 (см. коммент Мартина)
no subject
(см. коммент Мартина)
Идём по ссылке и ищем "martin". Потом размышляем над результатом.
Вот именно об этом я и говорю.
no subject
no subject
Короче говоря, кто-то передаёт список, кто-то пишет библиотеку. При соединении в коде это то работает, то не работает. Эксперт долго и нудно ищет причину, а потом громко ругается. "Обычный пользователь" видит чудо, ругает индийских программистов и склеивает на соплях с разными интересными эффектами в результате.
no subject
Если вы знаете язык программирования, в котором описанный сценарий можно реализовать простым способом, расскажите, пожалуйста.
no subject
Если язык или библиотека что-то не делают, это совсем не значит, что причина в том, что этого нельзя сделать.
Описанный сценарий у меня тоже вызывает большие сомнения. Но это уже как-нибудь в другой раз.
no subject
no subject