Тип гласного в Хаскеле, возможно ли это? - PullRequest
7 голосов
/ 02 октября 2011

Я написал следующий код для удаления гласных из предложения:

   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

Просто интересно, можно ли создать тип данных для гласной? Компилятор не позволит мне использовать символы в типе данных.

Ответы [ 2 ]

15 голосов
/ 02 октября 2011

Не напрямую. Проблема в том, что символы являются встроенным типом без возможности полиморфизма. Это отличается от числовых литералов, которые разработаны как полиморфные с помощью класса Num.

Тем не менее, есть два основных подхода, которые вы можете выбрать: обертка нового типа с умным конструктором или совершенно новый тип.

Обертка нового типа проще в использовании:

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

Здесь новый тип различает необработанный и очищенный ввод. Если единственный способ сделать CleanString - это cleanString, то вы статически знаете, что каждый CleanString должным образом очищен (при условии, что cleanString является правильным). В вашем случае кажется, что вам действительно нужен тип для согласных, а не гласных.

Новые типы в Haskell очень легковесны *, но программисту действительно приходится писать и использовать код для переноса и развертывания. Во многих случаях преимущества перевешивают дополнительную работу. Тем не менее, я не могу придумать ни одного приложения, в котором важно знать, что ваш String не содержит гласных, поэтому я, вероятно, просто поработаю с простым String.

* newtypes существуют только во время компиляции, поэтому теоретически нет затрат производительности при их использовании. Однако их существование может изменить созданный код (например, запретить ПРАВИЛА), поэтому иногда наблюдается ощутимое влияние на производительность.

8 голосов
/ 02 октября 2011

Вы можете использовать фантомные типы для обозначения символов дополнительной информацией, чтобы во время компиляции гарантировать, что система типов гарантирует, что ваши строки содержат только, например, гласные или не гласные.

Вот игрушечный пример:

{-# 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
...