如果我有数据并且我不确定这是整数还是字符串,但我想对它应用 (+1),如果它是一个很好的整数,但如果它是一个字符串 - 什么都不做,我将如何处理这?这是Nothing
进来的地方吗?
3 回答
好吧,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 有一个很好的技巧来避免上面的样板,这是fmap
从Functor
类中使用的。这让我们可以自动在Right
一半上编写函数,Either
而完全忽略一半中的任何内容Left
,如下所示:
f = fmap (+1)
如果我们推断上述函数的类型,它实际上是:
f :: (Functor f, Num a) => f a -> f a
但在我们的例子中,我们可以通过设置a
toInteger
和f
to来专门化类型Either String
:
f :: Either String Int -> Either String Int
换句话说,Either String
是一个类的实例的一个例子Functor
,其中有很多。
但是,请注意,为了在整数或字符串上使用此函数,您必须首先将它们包装在Left
或Right
构造函数中。例如,这些不会进行类型检查:
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]
我们不想混合类型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]
你会catMaybes
从Data.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]
这是一个Int
egers 或String
s 的列表,所以 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
它会打印一些数字,几个字符串,保存文件,打印更多数字,最后打印一个字符串。
如果您的值可能是两种类型之一,则可以使用该Either
值的类型。Either
将另外两种类型作为参数,因此type或 typeEither a b
的 value 也是如此。这两种情况由两个不同的构造函数标识,分别表示左 ( ) 或右 ( ) 类型。在您的情况下,您将使用. 要操作值,您可以使用模式匹配。例如,a
b
Left
Right
a
b
Either String Integer
foo :: Either String Int -> Either String Int
foo (Left s) = Left s
foo (Right n) = Right (n+1)
实现您在问题中询问的转换。