如何处理将函数应用于不正确的值?

时间:2012-09-17 19:22:17

标签: haskell

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

3 个答案:

答案 0 :(得分:8)

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

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的{​​{1}}一半上自动编写函数,同时完全忽略Either一半中的任何内容,如下所示:

Left

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

f = fmap (+1)

但在我们的案例中,我们可以通过将f :: (Functor f, Num a) => f a -> f a 设置为a并将Integer设置为f来专门化该类型:

Either String

换句话说,f :: Either String Int -> Either String Int Either String类实例的一个例子,其中有很多。

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

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

答案 1 :(得分:5)

我们不想混合类型Willy-Nilly

你说

  

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

Haskell不允许您创建这样的列表 - 列表中的所有元素必须具有相同的类型。您可以[1,2,3]["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)或者因为它无权不在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绒毛,而宁愿只得到整数。 如果[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

根据需要。

以有序的方式混合类型

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

String

这是Either Int String egers或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"] ,但也许你想混合的不只是数字和文字,也许你想要整数,字符串,时间或字符串列表。你可以自己滚动:

MyStuff

可以在

之类的列表中
MyString

(注意Haskell区分大小写; 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 标签中?好吧,也许它开始是一个文件或网络连接或其他东西的整个文本加载,你的解析器将它分成整数,字符串,日期和字符串列表,标记它们(很容易做到)。

...你可以通过编写一个根据它得到的数据定义不同的函数来分隔它们:

{{1}}

现在,如果你有这样的感觉,你可以做到

{{1}}

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

答案 2 :(得分:1)

如果您的值可以是两种类型之一,则可以使用Either类型作为该值。 Either将其他两种类型作为参数,因此Either a ba类型或b类型的值。这两个案例由两个不同的构造函数LeftRight标识,表示左侧(a)或右侧(b)类型。在您的情况下,您将使用Either String Integer。要操纵值,您可以使用模式匹配。例如,

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

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