#haskell #types #functional-programming #pattern-matching
#haskell #типы #функциональное программирование #сопоставление с образцом
Вопрос:
Я написал следующий код для удаления гласных из предложения:
main = print $ unixname "The House"
vowel x = elem x "aeiouAEIOU"
unixname :: [Char] -> [Char]
unixname [] = []
unixname (x:xs) | vowel x = unixname xs
| otherwise = x : unixname xs
Просто интересно, можно ли создать тип данных для гласного? Компилятор не позволяет мне использовать символы в типе данных.
Комментарии:
1. Я не знал, что гласные не разрешены в именах UNIX 😉
2. Зачем вам нужен «тип данных для гласного»? Что бы это сделало?
3. @Anschel, предположительно
Char
, значение которого может принимать только гласные. Это был бы способ объявить один из критериев правильности дляunixname
. Э … на самом деле не потому, что ему нужно его дополнение, но вы поняли идею.4. Это просто упражнение, данное в моем курсе CS, не знаю, почему он назвал его «unixname», для функции, которая удаляет гласные.
5. в качестве альтернативы вы могли бы использовать
unixname = filter consonant
whereconsonant = not . vowel
Ответ №1:
Не напрямую. Проблема в том, что символы являются встроенным типом без возможности полиморфизма. Это отличается от числовых литералов, которые предназначены для полиморфности через Num
класс type .
Тем не менее, вы можете использовать два основных подхода: оболочку newtype с интеллектуальным конструктором или совершенно новый тип.
Оболочку newtype проще использовать:
module Vowel (Vowel, vowel, fromVowel) where
newtype Vowel = Vowel Char
vowel :: Char -> Maybe (Vowel)
vowel x | x `elem` "aeiouAEIOU" = Just (Vowel x)
| otherwise = Nothing
fromVowel :: Vowel -> Char
fromVowel (Vowel x) = x
Поскольку Vowel
конструктор не экспортируется, новые Vowel
s могут быть созданы только vowel
функцией, которая допускает только нужные вам символы.
Вы также можете создать новый тип, подобный этому:
data Vowel = A | E | I | O | U | Aa | Ee | Ii | Oo | Uu
fromChar :: Char -> Maybe Vowel
fromChar 'a' = Just Aa
fromChar 'A' = Just A
-- etc.
toChar :: Vowel -> Char
toChar Aa = 'a'
toChar A = 'A'
Этот второй способ довольно тяжелый, и поэтому его гораздо сложнее использовать.
Итак, вот как это сделать. Я не совсем уверен, что вы этого хотите. Обычная идиома — создавать типы, которые представляют ваши данные, и вы специально не представляете гласные. Общий шаблон будет примерно таким:
newtype CleanString = Cleaned { raw :: String }
-- user input needs to be sanitized
cleanString :: String -> CleanString
Здесь newtype различает неочищенный и очищенный ввод. Если единственный способ сделать a CleanString
— это by cleanString
, то вы статически знаете, что каждый CleanString
из них правильно очищен (при условии, что cleanString
это правильно). В вашем случае, похоже, вам действительно нужен тип для согласных, а не гласных.
Новые типы в Haskell очень легкие *, но программисту приходится писать и использовать код для переноса и разворачивания. Во многих случаях преимущества перевешивают дополнительную работу. Тем не менее, я действительно не могу придумать ни одного приложения, в котором важно знать, что ваш String
не содержит гласных, поэтому я бы, вероятно, просто работал с простым String
.
* новые типы существуют только во время компиляции, поэтому теоретически их использование не требует затрат на производительность во время выполнения. Однако их существование может изменить созданный код (например, запрещающие правила), поэтому иногда наблюдается заметное влияние на производительность.
Ответ №2:
Вы могли бы использовать фантомные типы для пометки символов дополнительной информацией, чтобы система типов гарантировала во время компиляции, что ваши строки содержат только, например, гласные или не гласные.
Вот игрушечный пример:
{-# LANGUAGE EmptyDataDecls #-}
import Data.Maybe
newtype TaggedChar a = TaggedChar { fromTaggedChar :: Char }
data Vowel
data NonVowel
isVowel x = x `elem` "aeiouyAEIOUY"
toVowel :: Char -> Maybe (TaggedChar Vowel)
toVowel x
| isVowel x = Just $ TaggedChar x
| otherwise = Nothing
toNonVowel :: Char -> Maybe (TaggedChar NonVowel)
toNonVowel x
| isVowel x = Nothing
| otherwise = Just $ TaggedChar x
unixname :: [Char] -> [TaggedChar NonVowel]
unixname = mapMaybe toNonVowel
Преимущество этого подхода заключается в том, что вы все равно можете писать функции, которые работают со всеми TaggedChars независимо от тега. Например:
toString :: [TaggedChar a] -> String
toString = map fromTaggedChar