2

如果我有数据并且我不确定这是整数还是字符串,但我想对它应用 (+1),如果它是一个很好的整数,但如果它是一个字符串 - 什么都不做,我将如何处理这?这是Nothing进来的地方吗?

4

3 回答 3

8

好吧,Haskell 函数是强类型的,这意味着它们指定了输入和输出的类型。为了首先接受多个类型的值,您需要使用Either将它们保存在同一类型中。因此,例如,如果您想接收 aString或 an Integer,那么您的函数必须具有类型:

f :: Either String Integer -> ...

然后,您编写函数的方式是在 上进行模式匹配Either以查看您收到的值类型。你会写这样的东西:

-- str is a String
f (Left  str) -> ... 
-- int is an Integer
f (Right int) -> ...

所以,做你想做的最简单的方法是只增加数字,如果它是一个Integer,但String保持不变。你会这样写:

-- Don't do anything if it is a string
f (Left  str) = Left  str
-- Increment it if it is an integer
f (Right int) = Right (int + 1)

如果您要求编译器推断上述函数的类型,您将得到:

f :: Either String Int -> Either String Int

Haskell 有一个很好的技巧来避免上面的样板,这是fmapFunctor类中使用的。这让我们可以自动在Right一半上编写函数,Either而完全忽略一半中的任何内容Left,如下所示:

f = fmap (+1)

如果我们推断上述函数的类型,它实际上是:

f :: (Functor f, Num a) => f a -> f a

但在我们的例子中,我们可以通过设置atoIntegerfto来专门化类型Either String

f :: Either String Int -> Either String Int

换句话说,Either String是一个类的实例的一个例子Functor,其中有很多。

但是,请注意,为了在整数或字符串上使用此函数,您必须首先将它们包装在LeftRight构造函数中。例如,这些不会进行类型检查:

f 1   -- WRONG!
f "a" -- WRONG!

但这些将:

f (Right  1) -- Correct!
f (Left "a") -- Correct!

这意味着,如果你有一个整数列表和一个字符串,你将不得不这样写:

list = [Right 1, Left "a", Right 2]

请注意,如果您尝试在列表中混合整数和字符串,则会收到类型错误:

list = [1, "a", 2] -- WRONG!

然后我们可以映射f第一个正确list的得到:

map f list = [Right 2, Left "a", Right 3]
于 2012-09-17T19:29:38.783 回答
5

我们不想混合类型Willy-Nilly

你说

所以说我有一个列表,但这个列表可能是 ["a"] 或者它可能是 [1],如果我想在不知道列表中确切类型的情况下应用一些函数,

Haskell 不允许您制作这样的列表——列表中的所有元素都必须具有相同的类型。你可以有[1,2,3]or ["a","cheese","4"],但没有["a",1]。Haskell 将确保只应用适用于列表中元素的函数。Haskell 和您将始终能够计算出列表中元素的类型。这种类型知识被称为静态类型,乍一看似乎没有必要死板,但实际上它确实非常灵活。黄金法则是:编译器一直都知道你有什么数据。

我该如何处理类型问题?

你不会遇到这样的类型问题!这就是静态类型的乐趣——在数据类型或数据类型之间转换时出错的程序不会编译——你会在代码运行之前发现错误!

也许你有一个可能混合字符串和数字的数据流。您需要问的第一个问题是“我想用它们做什么,我真的需要将它们混合在一起吗?”。有没有更好的方法将它们分开?例如,在 Haskell 中,编写功能齐全的静态类型解析器非常容易(如果需要,请查找 parsec)。


新:忽略非整数

(在重新阅读您的问题标题时,也许这就是您所追求的。)

忽略非整数很容易。就像你提到的,你可以使用Nothing和它的对应物Just,像这样

myData = ["nan","45","3454.5","that's not an int, it's a float!","4","6"]

maybeInts :: [String] -> [Maybe Integer] -- type signature needed so Haskell knows you want Integers
maybeInts = map maybeRead 

它使用一个方便的函数maybeRead来转换每个值,但你需要import Network.CGI.Protocol (maybeRead)或者因为它无权不 in Data.Maybe,所以复制并粘贴代码:

maybeRead :: Read a => String -> Maybe a
maybeRead = fmap fst . listToMaybe . reads 
  -- take all parses, grab the first one, ignore the remaining text

您将获得的价值maybeInts myData将是

[Nothing, Just 45, Nothing, Nothing, Just 4, Just 6]

但也许你不想要所有这些Nothing/Just绒毛,而宁愿只得到整数。如果你hoogle[Maybe a] -> [a]你会catMaybesData.Maybe模块中找到,那么让我们import Data.Maybe定义

intsOnly :: [String] -> [Int]
intsOnly = catMaybes.map maybeRead  -- read them, ignore Nothings and unwrap Justs

所以当intsOnly myData你得到

[45,4,6]

然后如果你想给它们都加一个,你可以做

map (+1) (intsOnly myData)

这是

[46,5,7]

按要求。

有条不紊地混合类型

如果你必须混合整数和字符串,你可以这样做

[Left 5, Right "a", Right "hello", Left 6]

这是一个Integers 或Strings 的列表,所以 type Either Int String,但也许你想混合的不仅仅是数字和文本,也许你想要一个整数、一个字符串、一个时间或一个字符串列表。你可以自己滚动:

data MyStuff = MyInt Int | MyString String | MyDate DateTime | MyList FilePath [String] 
  deriving Show

可以在一个列表中,例如

mystuff = [MyInt 6,  MyString "Hello",  MyString "MyString isn't a great name", 
    MyList "C:/temp/stuff.txt" ["yeah","give","them","problem-specific", "names"], 
    MyInt 12,  MyString "instead"]

(注意 Haskell 区分大小写;MyStuff是一种类型,MyString是一种类型构造函数(一种将数据包装成类型的函数)并且mystuff是一个变量。所有变量都是常量!)

您的数据最终将如何像这样包装在My___标签中?好吧,也许它从文件中的整个文本负载或从网络连接或其他东西开始,然后您的解析器将其拆分为整数、字符串、日期和字符串列表,并在执行过程中对其进行标记(很容易做到)。

...并且您可以通过编写一个根据获取的数据定义不同的函数来将它们分开:

report :: MyStuff -> IO ()
report (MyInt n) = mapM_ print [1..n]   -- turn the numbers 1..n into print commands, then combine them
report (MyString xs) = print xs         -- just print the string.
report (MyList filename contents) 
    = writeFile filename (unlines contents) -- write them on seperate lines in the file
report _ = return ()                    -- if it's anything else, ignore it and do nothing.

现在,如果您愿意,您可以这样做

mapM_ report mylist

它会打印一些数字,几个字符串,保存文件,打印更多数字,最后打印一个字符串。

于 2012-09-17T20:56:42.400 回答
1

如果您的值可能是两种类型之一,则可以使用该Either值的类型。Either将另外两种类型作为参数,因此type或 typeEither a b的 value 也是如此。这两种情况由两个不同的构造函数标识,分别表示左 ( ) 或右 ( ) 类型。在您的情况下,您将使用. 要操作值,您可以使用模式匹配。例如,abLeftRightabEither String Integer

foo :: Either String Int -> Either String Int
foo (Left s) = Left s
foo (Right n) = Right (n+1)

实现您在问题中询问的转换。

于 2012-09-17T19:29:18.547 回答