将嵌套列表转换为自定义数据类型

时间:2012-08-29 15:01:40

标签: haskell

我正在尝试使用列表推导将嵌套列表转换为名为Mydata的自定义类型,如下所示:

main = do
    let a = [["12.345", "1", "4.222111"],
             ["31.2", "12", "9.1234"],
             ["43.111111", "3", "8.13"],
             ["156.112121", "19", "99.99999"]]
    let b = foo a
    print b

foo xss = [(xs,xs) | xs <- xss, xs <- xss]
    where
         xs = Mydata (read xs!!0 :: Float) (read xs!!1 :: Int) (read xs!!2 :: Float)

data Mydata = Mydata {valA :: Float, valB :: Int, valC :: Float}

当我运行程序时,我收到以下错误:

1.hs:11:28:
    Couldn't match expected type `String' with actual type `Mydata'
    In the first argument of `read', namely `xs'
    In the first argument of `(!!)', namely `read xs'
    In the first argument of `Mydata', namely `(read xs !! 0 :: Float)'

任何人都可以帮我弄清问题是什么吗?感谢。

3 个答案:

答案 0 :(得分:3)

列表推导中的

xs定义(可能是无意的)递归,没有任何意义。可能的实施如下:

data Mydata = Mydata {valA :: Float, valB :: Int, valC :: Float} deriving Show

-- rely on type inference instead of specifying explicit type for each `read'
dataFromList [a, b, c] = Mydata (read a) (read b) (read c)
dataFromList _ = error "dataFromList: not enough arguments in list"

main = do
    let a = [["12.345", "1", "4.222111"],
             ["31.2", "12", "9.1234"],
             ["43.111111", "3", "8.13"],
             ["156.112121", "19", "99.99999"]]
    let b = map dataFromList a
    -- alternatively
    -- let b = [dataFromList triple | triple <- a]
    print b

答案 1 :(得分:3)

{ - 我将借此机会告诉你一些我认为从长远来看会有所帮助的事情以及解决问题的方法。 - }

import Control.Applicative
import Data.Maybe
import Network.CGI.Protocol (maybeRead)

{ - Control.Applicative允许我使用<$><*>这些是非常方便的函数来处理大量的事情(稍后)。 我稍后会使用maybeRead。我不知道为什么它不在Data.Maybe

首先是数据结构。我已经派出了节目,所以我们可以打印Mydata。 - }

data Mydata = Mydata {valA :: Float, 
                      valB :: Int, 
                      valC :: Float}
  deriving Show

{ - 我已将somedata作为定义放在主体中(let a =内有main),因为我觉得你严重过度使用了IO monad。 值得尝试在纯粹的世界中尽可能多地做,因为它使调试更容易。 也许在你的实际问题中,你会从某处读到somedata,但是为了编写函数, 有一些像这样的测试数据是一个很大的好处。 (尽管只提到somedata作为定义 这里一次,所以你不会得到一些全局常量!) - }

somedata = [["12.345", "1", "4.222111"],
            ["31.2", "12", "9.1234"],
            ["43.111111", "3", "8.13"],
            ["156.112121", "19", "99.99999"]]

somewrong = [ ["1",   "2",   "3"     ],    -- OK
              ["1.0", "2",   "3.0"   ],    -- OK, same value as first one
              ["1",   "2.0", "3"     ],    -- wrong, decimal for valB
              ["",    "two",  "3.3.3"] ]   -- wrong, wrong, wrong.

{ - 让我们编写一个函数来读取单个Mydata,但使用Maybe Mydata,这样我们就可以优雅地恢复它,如果它没有用完。 maybeRead :: Read a => String -> Maybe a,因此它会将字符串转换为Just您想要的内容,或者如果不能则将Nothing转换为Either。 这比简单地使用错误消息崩溃更好。 (更好的方法是返回Right一条解释问题或readMydata_v1 :: [String] -> Maybe Mydata readMydata_v1 [as, bs, cs] = case (maybeRead as, maybeRead bs, maybeRead cs) of (Just a, Just b, Just c) -> Just $ Mydata a b c _ -> Nothing readMydata_v1 _ = Nothing -- anything else is the wrong number of Strings 答案的错误消息,但我今天要跳过它。)

我要写这三种方式,越来越好。 - }

(maybeRead as, maybeRead bs, maybeRead cs)

{ - 所以我们查看Mydata,如果它们都有效,我们会从中Just生成一个Nothing,然后返回Mydata正确答案, 但是如果发生其他事情,其中​​一个是Nothing,那么我们就无法制作map readMydata_v1 somedata,因此我们总体上得到map readMydata_v1 somewrong

使用Mydata a b ca在gchi中试用。

请注意,因为我使用了表达式b,它强制cFloatInt的类型为Float,{{1} }和(Just a, Just b, Just c)模式中的(maybeRead as, maybeRead bs, maybeRead cs)。 该模式是maybeRead的输出,它强制Maybe的三种用法的类型是正确的 - 我不需要提供个人类型签名。类型签名非常方便,但它们并不是在函数中间。

现在我喜欢使用case,但我不喜欢在彼此内部编写大量Maybe语句,因此我可以使用MonadMaybe的面}。 有关monad的更多详细信息,请参阅了解Haskell for Great Good http://learnyouahaskell.com,但就我的目的而言,我可以将IO值视为readMydata_v2 :: [String] -> Maybe Mydata readMydata_v2 [as,bs,cs] = do a <- maybeRead as b <- maybeRead bs c <- maybeRead cs return $ Mydata a b c readMydata_v2 _ = Nothing -- anything else is the wrong number of Strings 即使它不是。 - }

Maybe

{ - 我似乎写没有错误处理代码! a monad不是很棒! 在这里,我们接受maybeRead as我们可以从b获得的任何内容,无论bs我们可以从阅读c获得什么 以及我们从cs得到的Just $ Mydata a b c,如果这一切都奏效了,我们得到MaybeNothing monad通过停止并返回Nothing来处理我们获得的任何Just,并在Applicative中包含任何正确答案。

虽然这非常好,但它并不感觉非常函数式编程,所以让我们全力以赴,让它成为Applicative。 您应该在http://learnyouahaskell.com中了解foo x y z = do thing1 <- something x thing2 <- somethingelse y thing3 <- anotherthing x z thing4 <- yetmore y y z return $ somefunction thing1 thing2 thing3 thing4 ,但就目前而言,我们只需使用它即可。

每当你发现自己写作时

foo x y z = somefunction  <$>  something x  <*>  somethingelse y  <*>  anotherthing x z  <*>  yetmore y y z

这意味着当你可以更干净地使用“应用函子”时,你正在使用monad。 所有这些在实践中意味着你可以写出

foo x y z = somefunction  <$>  something x  
                          <*>  somethingelse y  
                          <*>  anotherthing x z  
                          <*>  yetmore y y z

或者如果您愿意,

<$>

这更好,因为(a)感觉更像普通的功能应用程序(请注意$的工作方式与<*>的工作方式类似于空格thing1)和( b)你不需要 发明名称something x

这意味着找出somethingelse yanotherthing x z以及yetmore y y zsomefunction的结果,然后将readMydata应用于结果。

让我们readMydata_nice :: [String] -> Maybe Mydata readMydata_nice [a,b,c] = Mydata <$> maybeRead a <*> maybeRead b <*> maybeRead c readMydata_nice _ = Nothing 采用适用的方式: - }

maybeRead a

{ - Aaaahhhhh,如此干净,如此功能,如此简单。 MMMMM。 :)多想想,少写。

这意味着需要使用maybeRead bmaybeRead c以及Mydata的结果并将Maybe应用于结果,但因为所有内容都是Nothing,如果有的话方式为Nothing,答案为map readMydata_nice somedata

同样,您可以使用map readMydata_nice somewrongmain

在ghci中对此进行测试

无论如何,让我们写main = mapM_ print $ catMaybes $ map readMydata_nice somedata ,现在它也更具功能性。 - }

somedata

{ - 这会将Maybe Mydata中的每个字符串列表记录下来,并将其作为Nothing读取,然后抛弃IO并将它们转换为print mapM_命令并执行它们相继。 map的工作方式有点像IO,但会创建每个print。因为它是几个catMaybes,所以每个都在一个单独的行上,这更容易阅读。

在这里,我决定使用Nothing忽略Either值,只打印那些有用的值。在真实的节目中, 我像我说的那样使用Maybe,这样我就可以传递错误信息,而不是默默地忽略错误的数据。我们在Either上使用的所有技巧也适用于{{1}}。 - }

答案 2 :(得分:1)

在定义中

where
  xs = Mydata (read xs!!0 :: Float) (read xs!!1 :: Int) (read xs!!2 :: Float)

您在定义的左侧和右侧使用xs。因此,它必须在两侧具有相同的类型。编译器假定xs的类型为MyData,并且您无法将read应用于该类型的值。