Reputation: 8073
I am trying to convert nested lists into a custom type called Mydata
using a list comprehension as follows:
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}
When I run my program I get the following error:
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)'
Can anyone help me figure out what the problem is? Thanks.
Upvotes: 1
Views: 508
Reputation: 32455
{- I'm going to take the opportunity to tell you some other things I think will help in the long run as well as fix the problem. -}
import Control.Applicative
import Data.Maybe
import Network.CGI.Protocol (maybeRead)
{-
The Control.Applicative lets me use <$>
and <*>
which are really handy functions for working with loads of things (more later).
I'm going to use maybeRead
later. I don't know why it's not in Data.Maybe
.
Data structure first. I've derived show so we can print Mydata. -}
data Mydata = Mydata {valA :: Float,
valB :: Int,
valC :: Float}
deriving Show
{-
I've put somedata
as a definition in the main body (you had let a =
inside main
), because I felt you were seriously overusing the IO monad.
It's worth trying to do as much as possible in pure world, because it makes debugging much easier.
Maybe in your actual problem, you'll have read somedata
in from somewhere, but for writing the functions,
having a bit of test data like this lying around is a big bonus. (Try though to only mention somedata
as a definition
here once, so you don't get a rash of global constants!)
-}
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.
{-
Let's write a function to read a single Mydata
, but use Maybe Mydata
so we can recover gracefully if it doesn't work out.
maybeRead :: Read a => String -> Maybe a
, so it turns strings into Just
what you want, or gives you Nothing
if it can't.
This is better than simply crashing with an error message.
(Better still would be to return Either
an error message explaining the problem or the Right
answer, but I'm going to skip that for today.)
I'm going to write this three ways, getting nicer and nicer. -}
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
{-
so we look at (maybeRead as, maybeRead bs, maybeRead cs)
and if they all worked, we make a Mydata
out of them, then return Just
the right answer,
but if something else happened, one of them was a Nothing
, so we can't make a Mydata
, and so we get Nothing
overall.
Try it out in gchi with map readMydata_v1 somedata
and map readMydata_v1 somewrong
.
Notice how because I used the expression Mydata a b c
, it forces the types of a
, b
and c
to be Float
, Int
and Float
in the (Just a, Just b, Just c)
pattern.
That pattern is the output of (maybeRead as, maybeRead bs, maybeRead cs)
, which forces the types of the three uses of maybeRead
to be right -
I don't need to give individual type signatures. Type signatures are really handy, but they're not pretty in the middle of a function.
Now I like using Maybe
, but I don't like writing loads of case
statements inside each other, so I could use the face that Maybe
is a Monad
.
See Learn You a Haskell for Great Good http://learnyouahaskell.com for more details about monads, but for my purposes here, it's like I can treat a Maybe
value like it's IO
even though it's not.
-}
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
{-
I seem to write no error handling code! Aren't Maybe
monads great!
Here we take whatever a
we can get out of maybeRead as
, whatever b
we can get from reading bs
and whatever c
we get from cs
, and if that has all worked we get Just $ Mydata a b c
.
The Maybe
monad deals with any Nothing
s we get by stopping and returning Nothing
, and wraps any correct answer in Just
.
Whilst this is really nice, it doesn't feel very functional programming, so let's go the whole hog and make it Applicative
.
You should read about Applicative
in http://learnyouahaskell.com, but for now, lets just use it.
Whenever you find yourself writing
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
it means you're using a monad when you could more cleanly use an "applicative functor". All this means in practice is that you could have written that
foo x y z = somefunction <$> something x <*> somethingelse y <*> anotherthing x z <*> yetmore y y z
or if you prefer,
foo x y z = somefunction <$> something x
<*> somethingelse y
<*> anotherthing x z
<*> yetmore y y z
this is nicer because (a) it feels more like ordinary function application (notice that <$>
works like $
and <*>
works like a space ) and (b) you don't need
to invent names
thing1
etc.
It means find out the results of something x
and somethingelse y
and anotherthing x z
and yetmore y y z
then apply somefunction
to the result.
Let's do readMydata
the applicative way:
-}
readMydata_nice :: [String] -> Maybe Mydata
readMydata_nice [a,b,c] = Mydata <$> maybeRead a <*> maybeRead b <*> maybeRead c
readMydata_nice _ = Nothing
{- Aaaahhhhh, so clean, so functional, so easy. Mmmmm. :) Think more, write less.
This means take the resuts of maybeRead a
and maybeRead b
and maybeRead c
and apply Mydata
to the result, but because everything is Maybe
, if anything along the way is Nothing
, the answer will be Nothing
.
Again, you can test this out in ghci with map readMydata_nice somedata
or map readMydata_nice somewrong
Anyway, lets write main
, which is now more functional too.
-}
main = mapM_ print $ catMaybes $ map readMydata_nice somedata
{-
This takes each list of Strings in somedata
and reads them as Maybe Mydata
, then throws away the Nothing
s and turns them into IO
print
commands and does them one after another.
mapM_
works a bit like map
but does every IO
it creates. Because it's several print
s, each one goes on a seperate line, which is much easier to read.
Here I've decided to use catMaybes
to ignore the Nothing
values and just print the ones that worked. In a real program,
I'd use Either
like I said, so that I can pass an error message instead of silently ignoring wrong data. All the tricks we used on Maybe
also work on Either
.
-}
Upvotes: 3
Reputation: 10260
xs
definition in list comprehension is (perhaps unintentionally) recursive, and makes no sense. Possible implementation follows:
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
Upvotes: 3
Reputation: 3173
In the definition
where
xs = Mydata (read xs!!0 :: Float) (read xs!!1 :: Int) (read xs!!2 :: Float)
you are using xs
on the left and the right-hand side of the definition. Therefore, it has to have the same type on both sides. The compiler assumes that the type of xs
is MyData
and you cannot apply read
to a value of that type.
Upvotes: 1