Замена символа в строке на строку в Haskell в стиле без точек - PullRequest
0 голосов
/ 23 апреля 2020

Идея состоит в том, чтобы написать функцию replace, которая принимает три аргумента, подстановочный знак, строку подстановки и строку ввода. Пример будет выглядеть как replace '*' "foo" "foo*" = "foobar". Обычно это не будет большой проблемой, я просто напишу что-нибудь рекурсивное и проверим, совпадает ли каждый символ в строке с моим подстановочным знаком. Тем не менее, мне нужно написать это в бессмысленном стиле. Я понятия не имею, как это сделать. Я знаю, что могу отбросить самый последний аргумент, входную строку, но после этого я застрял.

Мое решение без использования стилей без точек: replace wildcard sub = concatMap (\c -> if c==wildcard then sub else [c]).

Примечание: Нам не разрешено импортировать внешние библиотеки, т.е. нет Text.Replace.

Ответы [ 3 ]

2 голосов
/ 24 апреля 2020

Ясно, что писать бессмысленно просто ради бессмысленности глупо.

Причина, по которой полезно рассматривать бессмысленный стиль, заключается в том, что, если все сделано правильно, он ведет к более категориально-композиционный образ мышления. Поэтому, если мы хотим дать полезный ответ на такую ​​задачу, это то, о чем мы должны помнить.

concatMap - это хороший крючок в категории, потому что он просто >>= в списке монада. IOW, он поднимает список-стрелку Клейсли A->[B] в функцию списка-списка [A]->[B]. Итак, давайте сосредоточимся на том, как написать

useChar :: Char -> [Char]
useChar = \c -> if c==wildcard then sub else [c]

в виде стрелки. На самом деле я напишу это как функцию, но вы также можете вместо этого go добавить в категорию Клейсли.

Первое, что нужно отметить, это то, что вы копируете c. Как правило, это делается с помощью оператора разветвления

(&&&) :: Arrow (~>)
   => (b~>x) -> (b~>y) -> (b~>(x,y))

, поэтому

import Control.Arrow
sub = "SUBST"
useChar = (==wildcard)&&&(:[])
       >>> \(decision, embd) -> if decision then sub else embd

Обратите внимание, (:[]) является стрелкой Kleisli для монады списка; Я не собираюсь использовать это, хотя.

Теперь, решение if работает для логических значений, но логические значения некрасивые . Строго говоря, логическое значение - это просто тип суммы двух типов единиц

 Bool ≈ Either () () ≡ (()+())
(≡≡) :: Eq a => a -> a -> Either () ()
x ≡≡ y
 | x==y       = Right ()
 | otherwise  = Left ()

Мы могли бы также закодировать некоторую полезную информацию в любую из этих () опций и специально для Опция Right, которая явно является только константой sub.

constRight :: c -> Bool -> Either () c
constRight _ False = Left ()
constRight c True = Right c

useChar = ((==wildcard)>>>constRight sub) &&& (:[])
       >>> \(decision, embd) -> case decision of
              Left () -> embd
              Right theSub -> theSub

или в более общем виде

substRight :: c -> Either a b -> Either a c
substRight _ (Left a) = Left a
substRight c (Right _) = Right c

useChar = ((≡≡wildcard)>>>substRight sub) &&& (:[])
       >>> \(decision, embd) -> case decision of
              Left () -> embd
              Right theSub -> theSub

Очевидно, что мы можем заменить как левую, так и общую строку c operator

useChar = ((≡≡wildcard)>>>substRight sub) &&& (:[])
       >>> \(decision, embd) -> substLeft embd decision

Теперь, если мы повернем кортеж вокруг лямбды, он адаптируется к форме карри substLeft:

useChar = (:[]) &&& ((≡≡wildcard)>>>substRight sub) >>> uncurry substLeft
2 голосов
/ 23 апреля 2020

Функция, которую вы написали, не может быть уменьшена только с помощью Prelude, потому что нет способа уменьшить оператор if. (Эрих указывает, что это можно сделать с помощью bool из Data.Bool.) Здесь я придумываю альтернативное лечение, которое можно уменьшить, но я надеюсь, что к концу я убедил вас не делать этого.

Функция, которую вы можете найти здесь полезной: break. Из Hackage :

, примененного к предикату p и списку xs, возвращает кортеж, где первый элемент - самый длинный префикс (возможно, пустой) из xs элементов, которые не удовлетворяют p и второй элемент является остатком списка

Таким образом, мы можем построить функцию для разделения вашего списка на определенный элемент:

splitOnChar :: Char -> String -> (String, String)
splitOnChar char = break (char ==)

Оттуда мы можем разработать функцию сделать так, как вы описываете:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  case break (char ==) instr of
    (front, _:back) -> front ++ repstr ++ back
    _               -> error "Character to replace not found!"

Это позволяет вам избавиться от оператора if, который невозможно написать бессмысленно. С какой стати вы захотите написать эту точку бесплатно, это мне не по карману, но для этого нам нужно пожертвовать обработкой ошибок. Давайте посмотрим на версию, которая отбрасывает обработку

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  let ~(front, _:back) = break (char ==) instr
  in  front ++ repstr ++ back

Тогда мы можем заменить front и back на выражения:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  let split = break (char ==) instr
  in  (fst split) ++ repstr ++ (tail $ snd split)

Теперь давайте переместим split в конец оператора in:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  let split = break (char ==) instr
  in  (\a b -> a ++ repstr ++ b) <$> fst <*> tail . snd $ split

Теперь мы можем подставить split:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  (\a b -> a ++ repstr ++ b) <$> fst <*> tail . snd $ break (char ==) instr

Далее давайте сделаем eta-Reduce b из нашего лямбда-выражения:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  (\a -> ((a ++ repstr) ++)) <$> fst <*> tail . snd $ break (char ==) instr

Затем сделайте то же самое с a:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr instr =
  ((. (repstr ++)) . (++)) <$> fst <*> tail . snd $ break (char ==) instr

Затем замените instr:

replaceChar :: Char -> String -> String -> String
replaceChar char repstr =
  (((. (repstr ++)) . (++)) <$> fst <*> tail . snd) . break (char ==)

Теперь проще уменьшить char, поэтому мы Я просто переверну аргументы и вспомню flip их позже.

replaceChar :: String -> Char -> String -> String
replaceChar repstr char =
  (((. (repstr ++)) . (++)) <$> fst <*> tail . snd) . break (char ==)

А теперь мы действительно уменьшим char:

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  ((((. (repstr ++)) . (++)) <$> fst <*> tail . snd) .) . break . (==)

Теперь нам нужно переставить вся функция, чтобы получить repstr в конце. Начните с превращения . break . (==) в раздел:

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ ((((. (repstr ++)) . (++)) <$> fst <*> tail . snd) .)

Разрезание второй половины:

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ ((. (repstr ++)) . (++)) <$> fst <*> tail . snd

Раздел <*> tail . snd

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ (<*> tail . snd) $ ((. (repstr ++)) . (++)) <$> fst

Раздел <$> fst :

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ (<*> tail . snd) $ (<$> fst) $ (. (repstr ++)) . (++)

Раздел . (++):

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ (<*> tail . snd) $ (<$> fst) $ (. (++)) $ (. (repstr ++))

Unsection (. (repstr ++)):

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ (<*> tail . snd) $ (<$> fst) $ (. (++)) $ flip (.) $ (repstr ++)

Unsection (repstr ++):

replaceChar :: String -> Char -> String -> String
replaceChar repstr =
  (. break . (==)) $ (.) $ (<*> tail . snd) $ (<$> fst) $ (. (++)) $ flip (.) $ (++) repstr

Eta-Reduce:

replaceChar :: String -> Char -> String -> String
replaceChar =
  (. break . (==)) . (.) . (<*> tail . snd) . (<$> fst) . (. (++)) . flip (.) . (++)

И flip, чтобы вернуть аргументы в правильном порядке:

replaceChar :: Char -> String -> String -> String
replaceChar =
  flip $ (. break . (==)) . (.) . (<*> tail . snd) . (<$> fst) . (. (++)) . flip (.) . (++)

Et-voilà: совершенно неразборчивая куча гиббери sh волшебным образом делает то, что вам нужно, без какой-либо причины, понятной человечеству.

1 голос
/ 23 апреля 2020

Поскольку я не полностью согласен с @Andrew Ray, что преобразование вашей версии функции в стиль без точек невозможно, я хочу представить другую возможность.

Мы можем заменить if пункт с bool из Data.Bool. Прежде всего, мы рассмотрим лямбда-предложение (\c -> if c==wildcard then sub else [c]). Чтобы освободить нашу replace функцию, я сначала добавил helper, который принимает два дополнительных аргумента: строку замены и предикат, решая, какие символы оставить, а какие заменить:

helper :: [b] -> (b -> Bool) -> b -> [b]
helper repl pred c = if pred c then repl else [c]

Используя bool, мы избавляемся от предложения if. Обратите внимание на обратный порядок ветвей if и else, потому что bool работает наоборот.

helper repl pred c = bool [c] repl $ pred c

Поскольку c используется дважды в теле helper, мы можем использовать экземпляр функций Applicative для применения c к нескольким функциям с использованием функций liftAN. Хотя repl не нужно c, мы можем превратить его в функцию, которая принимает c, используя const. Таким образом, мы используем liftA3 сейчас:

helper repl pred c = liftA3 bool ((:[])) (const repl) pred $ c

Теперь мы можем легко нарезать c и pred:

helper repl = liftA3 bool ((:[])) (const repl)

и использовать композицию функций для замены (в общем ) f (g x) с помощью f . g $ x, мы можем переписать это следующим образом:

helper repl = liftA3 bool ((:[])) . const $ repl

Где, опять же, мы можем нарезать repl:

helper = liftA3 bool ((:[])) . const

Теперь давайте рассмотрим основные добавив туда нашего помощника.

replace :: (Foldable t, Eq b) => b -> [b] -> t b -> [b]
replace wildcard sub = concatMap (helper sub (==wildcard))

Поскольку нам нужно wildcard и sub в другом порядке, мы flip helper:

replace wildcard sub = concatMap ((flip helper) (==wildcard) sub)

получить sub из второго слагаемого, мы можем префикс (.):

replace wildcard sub = (.) concatMap ((flip helper) (==wildcard)) $ sub

, где мы можем легко нарезать sub:

replace wildcard = (.) concatMap ((flip helper) (==wildcard))

Третий член может быть переписано с композицией функций:

replace wildcard = (.) concatMap ((flip helper) . (==) $ wildcard)

Теперь раздел префикса (.):

replace wildcard = (concatMap .) ((flip helper) . (==) $ wildcard)

Наконец, мы можем применить шаблон f (g x) = f . g $ x снова:

replace wildcard = (concatMap .) . ((flip helper) . (==)) $ wildcard

где мы можем нарезать wildcard. Заменив helper нашим определением, мы получим:

replace = (concatMap .) . ((flip $ liftA3 bool ((:[])) . const) . (==))

Хотя это может быть немного более читабельным, чем решение @Andrew Ray, я все же настоятельно рекомендую вам не использовать стиль без точек таким образом , Оригинал был намного более читабельным, чем эта чепуха.

...