Pull to refresh

Comments 24

Размышления — это хорошо.
Реальность несколько интереснее.
1) Пара является Функтором
2) Подобные формулировки inc (x1, x2) = (inc x1, inc x2), наверняка требуют RankNTypes расширения. Включите его.
3) Вам Overlapping instances говорит о конфликте. Причём, с разным результатом для разных инстансов.
Самым простым, будет создать функцию
{-# LANGUAGE RankNTypes #-}
both :: (Num a, Num b) => (forall c. Num c => c -> c) -> (a, b) -> (a, b)
both f (a, b) = (f a, f b)

и вычислять надо:
> both inc (2, 3.0)
(3, 4.0)

4) То, что вы пытаетесь определить как Функтор2 почти наверняка является БиФунктором.
hackage.haskell.org/package/bifunctors-5
class Bifunctor p where
  -- | Map over both arguments at the same time.
  bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
  bimap f g = first f . second g

  -- | Map covariantly over the first argument.
  first :: (a -> b) -> p a c -> p b c
  first f = bimap f id


  -- | Map covariantly over the second argument.
  second :: (b -> c) -> p a b -> p a c
  second = bimap id


instance Bifunctor (,) where
  bimap f g ~(a, b) = (f a, g b)
наверняка требуют RankNTypes расширения. Включите его.


Интересно, почему мне об этом ничего не сказал компилятор?

Самым простым, будет создать функцию


Увы, решение с отдельной функцией не подходит. Мне нужно либо вообще исключить кортежи из класса функторов, либо обеспечить единую обработку, но с нужным мне поведением.

Скажем так, если я заведу отдельную функцию both, то мне придется придумать, как реализовать вызов вида

both inc [1, 2, 3, 4, 5]


То, что вы пытаетесь определить как Функтор2 почти наверняка является БиФунктором.


Да, это классно. Теперь попробую еще поискать трифункторы. В другом комментарии я отметил, что это проблема семантического поиска. Я понимаю, что мне нужно, но не могу сказать, как именно это называется. Так что, hoogle не всегда рулит :-).
2) Подобные формулировки inc (x1, x2) = (inc x1, inc x2), наверняка требуют RankNTypes расширения. Включите его.

Не-а. Я сначала тоже подумал, что это ж полиморфизм второго порядка, как оно вообще компилится! Потом допёр, что в этой функции три разных inc'а.
> Очень удивительно (я бы даже сказал — внезапно!), но кортеж-пара в GHC является функтором.
Ничего удивительного в этом нет. Все по аналогии с тем, что функтором является левое сечение стрелки (e ->). Поскольку запятая — тип с двумя параметрами (,) :: a -> b -> (a,b), то ее левое сечение (a,) имеет однопараметрический по b тип (a,) :: b -> (a,b) и становится функтором. Теперь должно стать понятно, почему fmap (+1) (2,3) вернет (2, 4).
А я догадался о правильном поведении по аналогии с Either. Спасибо за более общее объяснение.
Вообще, нам необходимо для разного поведения для фунторов и пар создать новый тип данных:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}

instance Incable Int where
    inc = (1+)

newtype Tup a = Tup {unTup :: a}

instance (Incable t1, Incable t2) => Incable (Tup (t1, t2)) where
    inc (Tup (x1, x2)) = Tup (inc x1, inc x2)

instance (Incable t1, Incable t2, Incable t3) => Incable (Tup (t1, t2, t3)) where
    inc (Tup (x1, x2, x3)) = Tup (inc x1, inc x2, inc x3)


и использовать:
> unTup $ inc $ Tup (2, 3.0)
(3,4.0)

> unTup $ inc $ Tup (2, 3.0, 8.0)
(3,4.0,9.0)
Разумно, но в общем контексте не пойдет. В оригинальном проекте мне нужно, чтобы изменилось поведение именно для кортежей, которые используются в коде, который мне недоступен. В следующей статье я собираюсь объяснить этот момент.
Ваша проблема в том, что вы желаете использовать функторы, и кортежи, но кортежи вы не хотите использовать как функторы.
Вы хотите нарушить математику, не стоит этого делать.
Двупараметрический тип попытались использовать как однопараметрический, редуцировав совершенно произвольно первый по порядку параметр. Или математика запрещает рассматривать другие случаи, когда нужно редуцировать, скажем, последний параметр, а остаток рассматривать как функтор?

Впрочем, в аппендиксе я все развернул. Я всего лишь против того, чтобы частное решение выносилось на уровень системы, тем более, в условиях, когда последствия такого решения невозможно заблокировать. Математика тут ни при чем.
Так и Хаскель не запрещает, только надо использовать newtype, для которого и определить функтор иначе. Возможно, было бы удобнее, если б инстансы были именованные, и можно было бы их не импортировать, а определять нужные себе в данный момент, хоть бы и даже у функции в where.
Создание новых типов в таких случаях плодит лишние сущности, иногда подобные типы «выламываются» из общей системы, которую приходится под них адаптировать. А если еще добавить к этому, что это расширение библиотеки, которая не тобой писана… Увы, на частном примере это не очень хорошо заметно, поэтому и кажется, что проблема искусственная. В реальной ситуации, когда в системе десятки типов, это становится заметно, но как впихнуть такой пример в формат статьи, я пока не очень понимаю. Постараюсь все же развернуть тему чуть позднее.

Да, именованные экземпляры были бы неплохим решением. Интересно, что мешает их реализовать? Еще одну возможность я вижу в том, чтобы предоставить возможность перекрытия экземпляров в пределах модуля. В случае паранойи можно было бы предусмотреть расширение, которое отключающает проверку перекрытия экземпляров в некоторых случаях. А то получается, как в известной присказке — «Хаскель не даст вам прострелить свою ногу» :-).

Насчет «нужных в данный момент». Слишком часто такие определения многократно повторяются. Иначе не возникало бы желание обобщить их. Но не больше, чем это необходимо :-).
В случае паранойи можно было бы предусмотреть расширение, которое отключающает проверку перекрытия экземпляров в некоторых случаях.

Так это, OverlappingInstances же?
Да, именованные экземпляры были бы неплохим решением. Интересно, что мешает их реализовать?

В идрисе, кстати, так и реализовали. Странно, что в хаскеле нет хотя бы расширения.
> Допустим, мы определяем класс Incable инкрементальных типов:
Вдогонку: зачем нам еще один класс инкрементальных типов? Уже есть class Enum с функцией succ из коробки.
Честно говоря, Incable — только для демонстрации. В оригинале был класс Reducable, но думаю, это вряд-ли кому-то тут что-то скажет. Скажу честно, использовать для демонстрации Enum просто не догадался. Не в том суть, а подобные мелочи можно шлифовать бесконечно.
И кстати, экземплярами Incable могут быть также дробные числа и даже кортежи :-).
Принято считать, что функтор — это всегда тип, у которого есть только один параметр.

Э… Функтором может быть любой тип подходящего кайнда и ничего странного в этом нет. Примером може служить реализация функтора для Map a b:
instance Functor (Map k) where
   -- функция применяется к значениям, а ключи не затрагиваются

И от словаря вполне логично ожидать такого поведения (и именно так реализован функтор для Map). А, скажем, от ячейки лабиринта Cell Int Int a можно ожидать, что функция не затронет координаты ячейки, а коснется только содержимого. И это только вполне очевидные примеры.
Не, ну понятно, что путем частичного вычисления любой тип можно свести к однопараметрическому. Интереснее, когда функтор воздействует на все свои части. Вот, оказалось, что я заново открыл бифункторы. Знать бы еще сразу, что они так называются. Но в мне также интересны трифункторы. Я ведь задачу поставил в более общем виде.
Может это слегка грубо, но мне кажется, что единственный вывод, который можно тут сделать это то, что перед тем, как писать статью стоит зайти на сайты где можно получить подробные ответы на вопросы и удостовериться, что вы правильно понимаете обсуждаемые вопросы. Примеры таких сайтов это (stack-overflow по тегу haskell, #haskell на freenode.net, рассылка Haskell-Beginners или haskell-cafe (в первой наверняка ответят подробнее), вопросы на хабрахабр, так же есть неплохие сообщества в LJ, Juick.com, ruhaskell.org).
Совет хороший, и я честно искал ответы на свои вопросы. Беда тут скорее в том, что «для того, чтобы правильно задать вопрос, нужно знать, как минимум, половину ответа» ©.

Вполне возможно, что я неправильно понимаю некоторые вещи, но чтобы грамотно их изложить, в чем именно мое непонимание, приходится очень много объяснять. Поверьте, то, чем я поделился — только небольшой кусок проблем, с которыми я столкнулся. Мне просто удалось выделить этот кусок в чистом виде.
Мне кажется, что утверждение было «в хорошем вопросе содержится половина ответа», не уверен, что из этого следует, то что её нужно знать, во всяком случае осознавать.

Впрочем это не важно, даже не зная половины ответа можно начать дискуссию и сайты (кроме сайтов вопросов) для этого и созданы, это поможет выяснить разницу и ошибки в понимании (возможно и у отвечающих). Просто в данной статье я заметил, некоторые вещи, которые меня удивили, так то: описание ожидаемого, не учитывание принципов работы выбора инстанса, неаккуратное соотвествие в записи того, что хочется получить и что делается (комментаний про Rank2Types). Я не знаю является ли это непониманием, или просто неточной формулировкой или неточным моим прочтением, но преварительный разбор частей бы не помешал :)
У Вас в определении тройки как функтора ошибка — fmap в правой части. Вот так работает:
instance Functor ((,,) t1 t2) where
    fmap f (x1, x2, x3) = (x1, x2, f x3)
Спасибо! Честно говоря, просто замылился глаз, когда писал код, а компилятор не дал вменяемого сообщения — предложил заняться выводом типа, а не проверить вызов.

Приятно также, что увеличение сразу трех элементов теперь также работает. То есть, как минимум одну свою проблему я все-таки решил :-).
Вы забываете о том, что у вас в кортеже могут быть значения совершенно разных типов. Давайте попробуем представить, что компилятор позволил вам реализовать fmap для тройки так, как вы хотите, и попробуем применить fmap (+1) к тройке:

fmap (+1) (True, "string", 2) == (True + 1, "string" + 1, 2 + 1)

Какой результат вы хотели бы получить в этом случае?

Теперь понятно, почему функтором можно сделать только однопараметрический тип (или многопараметрические, но частично применённые до однопараметрического)?

Если бы кортежи могли содержать значения только одного типа, то у вас всё бы получилось.
Sign up to leave a comment.

Articles