juan_gandhi: (VP)
Juan-Carlos Gandhi ([personal profile] juan_gandhi) wrote2013-01-23 07:37 pm

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. 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)

[identity profile] xeno-by.livejournal.com 2013-01-24 11:31 am (UTC)(link)
Касательно самого понятия "сложность", у меня нет однозначного мнения. С одной стороны, хотелось бы, чтобы вещи вроде [1] писались на раз-два. С другой, стороны, может, такие задачи просто сами по себе очень сложны, поэтому вполне естественно, что не всегда получится бац-бац и в продакшен [2].

[1] http://yz.mit.edu/wp/true-scala-complexity/
[2] http://news.ycombinator.com/item?id=3443436 (см. коммент Мартина)

[identity profile] vit-r.livejournal.com 2013-01-24 12:08 pm (UTC)(link)
Я пытаюсь прикинуть, что получится в реальной ситуации. И получающееся мне совсем не нравится.

(см. коммент Мартина)

Идём по ссылке и ищем "martin". Потом размышляем над результатом.

Вот именно об этом я и говорю.

[identity profile] xeno-by.livejournal.com 2013-01-24 12:10 pm (UTC)(link)
Я не очень понимаю, о чем именно вы говорите. Если поясните, буду благодарен.

[identity profile] vit-r.livejournal.com 2013-01-24 12:19 pm (UTC)(link)
Мартина там нет. И кто такой Мартин можно узнать только, если поднять какой-то левый контекст, на который ссылок нет.

Короче говоря, кто-то передаёт список, кто-то пишет библиотеку. При соединении в коде это то работает, то не работает. Эксперт долго и нудно ищет причину, а потом громко ругается. "Обычный пользователь" видит чудо, ругает индийских программистов и склеивает на соплях с разными интересными эффектами в результате.

[identity profile] xeno-by.livejournal.com 2013-01-24 12:24 pm (UTC)(link)
Вообще-то работать перестало только когда человек попытался расширить стандартные коллекции способом, который в других языках попросту невозможен. (Причем по концовке-то все завелось).

Если вы знаете язык программирования, в котором описанный сценарий можно реализовать простым способом, расскажите, пожалуйста.
Edited 2013-01-24 12:26 (UTC)

[identity profile] vit-r.livejournal.com 2013-01-24 12:45 pm (UTC)(link)
попросту невозможен

Если язык или библиотека что-то не делают, это совсем не значит, что причина в том, что этого нельзя сделать.

Описанный сценарий у меня тоже вызывает большие сомнения. Но это уже как-нибудь в другой раз.

[identity profile] xeno-by.livejournal.com 2013-01-24 12:27 pm (UTC)(link)
Про Мартина виноват. Для тех, кто будет читать обсуждение позже, я имел ввиду пользователя modersky.

[identity profile] vit-r.livejournal.com 2013-01-24 12:41 pm (UTC)(link)
Это не "виноват", это "высказывание сделано с предположением о наличии у программиста определённых знаний". То есть, это не ошибка. По крайней мере, не случайная ошибка.