Можно ли написать шаблонные функции в беспривязной форме?
Рассмотрим следующий код на Haskell.
data Keypress = Keypress Int Char
getSeq :: Keypress -> [Char]
getSeq (Keypress i c) = replicate i c
Есть ли способ написать getSeq
в бессмысленной форме?
getSeq
Это определение настолько похоже на его сопоставление с образцом, что кажется, что есть какой-то способ использовать карри или монады или что-то, чтобы избежать указания i
а также c
параметры. Однако http://pointfree.io/ не разрешает вывод без точек для getSeq
Думаю, из-за совпадения шаблонов.
Является ли это возможным?
3 ответа
Часто бывает полезно, особенно для рекурсивных типов данных с несколькими конструкторами, определить катаморфизм, способ свернуть структуру данных, когда для каждой из возможных конструкторов предоставляется одна функция.
Например, для Bool
катаморфизм
bool :: a -> a -> Bool -> a
bool x _ False = x
bool _ y True = y
и для Either
это
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f g (Left x) = f x
either f g (Right x) = g x
Более продвинутый катаморфизм - это тот, что для списков, который вы, вероятно, видели раньше: foldr
!
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f init [] = init
foldr f init (x:xs) = f x (foldr f init xs)
Мы обычно так не думаем (или, по крайней мере, я так не думаю), но foldr
является катаморфизмом: он обрабатывает сопоставление с образцом и рекурсивно деконструирует список для вас, при условии, что вы предоставляете "обработчики" для значений, найденных в двух конструкторах [a]
:
- Один случай для обработки
[]
, не нуждаясь ни в каких аргументах: просто значение типаb
- Один случай для обработки
(x:xs)
, Этот случай принимает один аргумент, представляющийx
заголовок списка и один аргумент, представляющий результат рекурсивного сворачивания хвоста, значение типаb
,
Катаморфизм менее интересен для типа с одним конструктором, но мы можем легко определить его для вашего Keypress
тип:
key :: (Int -> Char -> a) -> Keypress -> a
key f (Keypress x c) = f x c
В некотором смысле, катаморфизм позволяет вам абстрагироваться от части сопоставления с образцом определения функции, после чего вы можете просто работать с функциями, которым больше не нужно напрямую касаться базового типа данных.
Определив эту полезную функцию один раз, вы можете использовать ее много раз для реализации любой бессмысленной функции, которую пожелает ваше сердце. В вашем случае вы могли бы просто написать
getSeq :: Keypress -> [Char]
getSeq = key replicate
Как написано, нет. Но вы можете это исправить, если хотите:
data Keypress = Keypress
{ count :: Int
, char :: Char }
затем
getSeq p = replicate (count p) (char p)
и это может быть преобразовано в
getSeq = replicate <$> count <*> char
Вы не можете сделать это без небольшого количества образца, но lens
может создать этот шаблон для вас, так что это, вероятно, так близко, как вы собираетесь получить.
С помощью makePrisms
от Control.Lens.TH
будет генерировать Iso
, что, по сути, является первоклассным сопоставлением с шаблоном для типа данных с одним конструктором. Удобно, Iso
s двунаправлены, так что вы можете view
их (чтобы получить значения, такие как сопоставление с образцом) и review
их (чтобы вернуть значение обратно, как обычно используя конструктор).
С помощью makePrisms
а также view
можно написать getSeq
бессмысленно:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Keypress = Keypress Int Char
makePrisms ''Keypress
getSeq :: Keypress -> [Char]
getSeq = uncurry replicate . view _Keypress
Это лучше? Без понятия. Совпадение паттернов в вашем коде выглядит для меня просто отлично, но если вы используете lens
уже в любом случае, эта версия может быть более приятной для вас.