#haskell #functor
#haskell #функтор
Вопрос:
У меня возникли проблемы с написанием моего собственного экземпляра функтора для пользовательского типа данных (который я не могу изменить). Типы данных определяются как:
data Foo a = Baz String (Qux -> Foo a) | Bar a
data Qux = None | Quux String
Моя проблема заключается в написании функтора для Foo
типа. В частности, я не уверен, как правильно применить мою функцию функтора f
к функции в Foo
. Я думал о том, чтобы как-то вызвать функцию в конструкторе, но поскольку у меня нет ни одного Qux
, доступного для использования, я застрял.
instance Functor Foo where
fmap f (Bar a) = Bar (f a)
fmap f (Baz s ???) = Baz s (???) -- What goes here?
-- Clearly, something like this doesn't work
-- fmap f (Baz s g) = Baz s (f g)
-- I've also tried something like this, but I'm not sure where to go from there
-- fmap f (Baz s (None -> Bar b)) = Baz s (f b) ???
-- fmap f (Baz s (Quux x -> Bar b)) = Baz s ???
Комментарии:
1. Первая часть проста:
Baz
это изделие двух типов, поэтому вам просто нужно сопоставить шаблон наBaz s g
(гдеg :: Qux -> Foo a
). Что делать сg
— это сложная часть 🙂2. Как часто бывает в Haskell, способ думать об этом — просто «следовать типам». У вас есть функция
f :: a -> b
, функция (которую я вызовуg
) типаQux -> Foo a
, и вам нужна функция типаQux -> Foo b
. Я почти уверен, что есть только один способ сделать это с тем, что у вас есть здесь — и чтобы увидеть это, не бойтесь использовать рекурсию 😉
Ответ №1:
Давайте начнем с заполнения левой части этого уравнения. Мы можем написать g
, чтобы привязать вашу функцию к переменной.
fmap f (Baz s g) = Baz s (???)
Затем нам нужно заполнить вопросительные знаки другой функцией, которая принимает Qux
и возвращает Foo b
.
fmap f (Baz s g) = Baz s (q -> ???)
Мы можем сделать только одну вещь с q
, которая применяется g
к нему.
fmap f (Baz s g) = Baz s (q -> g q)
Однако это дает нам Foo a
, но нам нужно Foo b
! У нас нет функции для этого. Однако у нас есть f
, который принимает a
и возвращает b
. Если бы только был способ взять a -> b
и превратить это в Foo a -> Foo b
… о, подождите, есть, это называется fmap
!
fmap f (Baz s g) = Baz s (q -> fmap f (g q))
Если вы хотите записать функцию в нотации без точек (https://wiki.haskell.org/Pointfree ), вы можете сделать это вместо этого.
fmap f (Baz s g) = Baz s (fmap f . g)
Комментарии:
1. Большое спасибо! Именно с этим у меня возникли проблемы. Я забыл, что могу использовать подобные лямбды.
Ответ №2:
Следуйте типам:
fmap f (Baz s g) = GOAL
-- f :: a -> b
-- s :: String
-- g :: Qux -> Foo a
-- GOAL :: Foo b
Итак, мы ищем Foo b
(целевую линию). Мы можем создать его с помощью Bar
или Baz
. fmap
следует сохранить структуру такой, какой она должна быть Baz
. String
Аргумент Baz
, вероятно, не должен меняться, это только второй аргумент, который создает проблему.
fmap f (Baz s g) = Baz s GOAL
-- f :: a -> b
-- s :: String
-- g :: Qux -> Foo a
-- GOAL :: Qux -> Foo b
Теперь мы должны создать Qux -> Foo b
. Если вам нужно создать функцию, lambda является незаменимым инструментом (есть и другие, но lambda — это дедушка). Итак, создайте лямбда:
fmap f (Baz s g) = Baz s (x -> GOAL)
-- f :: a -> b
-- s :: String
-- g :: Qux -> Foo a
-- x :: Qux <-- NEW
-- GOAL :: Foo b
Обратите внимание, что теперь у нас есть x :: Qux
для работы. Используя g
и x
, мы можем создать Foo a
, а затем, используя fmap f
рекурсивно1, мы можем создать требуемое Foo b
.
Обратите внимание, как я просто заполняю выражение по одному крошечному шагу за раз, заменяя неизвестные аргументы целями, затем делаю шаг назад, чтобы рассмотреть, какие переменные у меня есть в области видимости и каковы их типы. Продолжайте делать это, пока не станет ясен путь к цели.
1 Рекурсивный тип обычно будет иметь соответствующее рекурсивное fmap
определение. Место, в котором происходит рекурсия, fmap
в точности соответствует способу рекурсии типа.