Можно ли написать шаблонные функции в беспривязной форме?

Рассмотрим следующий код на 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, что, по сути, является первоклассным сопоставлением с шаблоном для типа данных с одним конструктором. Удобно, Isos двунаправлены, так что вы можете 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 уже в любом случае, эта версия может быть более приятной для вас.

Другие вопросы по тегам