Haskell 中的元音数据类型可能吗?

发布于 2024-12-07 21:17:10 字数 311 浏览 0 评论 0原文

我编写了以下代码来从句子中删除元音:

   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

只是想知道是否可以为元音创建数据类型?编译器不允许我在数据类型中使用字符。

I have written the following code to remove vowels from a sentence:

   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

Just wondering if it is possible to create a data type for vowel? The compiler won't let me use characters in a data type.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

瑕疵 2024-12-14 21:17:10

不直接。问题在于字符是内置类型,不具备多态性。这与数字文字不同,数字文字被设计为通过 Num 类型类实现多态。

也就是说,您可以采用两种基本方法:带有智能构造函数的新类型包装器,或全新类型。

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 只能由 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 仅存在于编译时,因此理论上使用它们不会产生运行时性能成本。然而,它们的存在可能会改变生成的代码(例如禁止规则),因此有时会产生可测量的性能影响。

Not directly. The problem is that characters are a built-in type with no facility for polymorphism. This is different from numeric literals, which are designed to be polymorphic via the Num type class.

That said, there are two basic approaches you can take: a newtype wrapper with a smart constructor, or a totally new type.

The newtype wrapper is easier to use:

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

Since the Vowel constructor isn't exported, new Vowels can only be created by the vowel function, which only admits the characters you want.

You could also make a new type like this:

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'

This second way is pretty heavyweight, and therefore is much more awkward to use.

So that's how to do it. I'm not quite certain that you want to though. The usual idiom is to make types that represent your data, and you specifically don't represent vowels. A common pattern would be something like this:

newtype CleanString = Cleaned { raw :: String }

-- user input needs to be sanitized
cleanString :: String -> CleanString

Here the newtype differentiates between unsanitized and sanitized input. If the only way to make a CleanString is by cleanString, then you know statically that every CleanString is properly sanitized (provided that cleanString is correct). In your case, it seems you actually need a type for consonants, not vowels.

Newtypes in Haskell are very lightweight*, but the programmer does have to write and use code to do the wrapping and unwrapping. In many instances the benefits outweigh the extra work. However, I really can't think of any application where it's important to know that your String is vowel-free, so I'd probably just work with a plain String.

*newtypes only exist at compile-time, so in theory there's no runtime performance cost to using them. However, their existence can change the produced code (e.g. inhibiting RULEs), so sometimes there is a measurable performance impact.

未央 2024-12-14 21:17:10

您可以使用 幻像类型 来标记具有额外信息的字符,以便在运行期间使类型系统保证编译时您的字符串仅包含元音或非元音等。

这是一个玩具示例:

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

这种方法的好处是您仍然可以编写适用于所有 TaggedChar 的函数,而不管标签如何。例如:

toString :: [TaggedChar a] -> String
toString = map fromTaggedChar

You could use phantom types to tag characters with extra information, in order to make the type system guarantee during compile time that your strings only contain, for example, vowels or non-vowels.

Here's a toy example:

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

The benefit of this approach is that you can still also write functions that work on all TaggedChars regardless of the tag. For example:

toString :: [TaggedChar a] -> String
toString = map fromTaggedChar
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文