Reputation: 613
I'm pretty new to haskell and don't get how to work with Maybe [a]
. Normally I'm coding OOP (VB.NET) and in my free time I want to learn haskell (don't ask why ;) ).
So, what do I want to do? I want to read two files with numerical IDs and find out only the IDs that match in both files. Reading the files is not a big thing, it works wonderfully easy. Now, I get two lists of Maybe[Ids]
(for a simple example, just think the IDs are Int). So the function I need looks like this
playWithMaybe :: (Maybe [a]) -> (Maybe [Int]) -> [Int]
Now I want to access list members as I used to like this
playWithMaybe (x:xs) (y:ys) = undefined
But unfortunately it's not allowed, GHC says for both lists
Couldn't match expected type ‘Maybe [Int]’ with actual type ‘[t0]’
So I played around a little bit but didn't find a way to access list members. Can someone help me out? A little bit of explanation would be a great thing!
Upvotes: 5
Views: 10745
Reputation: 1137
Like others have said, [Int]
and Maybe [Int]
are not the same thing. Maybe [Int]
includes the extra information that the list may or may not be there. You said that you read the Ids from files. Perhaps, the Maybe
signifies whether the file existed or not, while the empty list signifies that the file did exist but contained no Ids.
If you want to work with the list, you first need to define what to do if there is no list. You can use this function to extract the list:
fromMaybe :: a -> Maybe a -> a
Maybe you want to treat having no list the same as having an empty list:
fromMaybe [] :: Maybe [a] -> [a]
Maybe you want to crash the entire program:
fromMaybe (error "Missing list") :: Maybe a -> a
There is also the more general maybe
function, which you can use if the default value is not of the same type as what's contained in the Maybe
:
maybe :: b -> (a -> b) -> Maybe a -> b
Both these functions are defined in the module Data.Maybe
.
You can also just work with the lists as if they existed and worry about their existence later by using Applicative
. You said you wanted to find the Ids common to both lists. You can do it like this:
maybeCommonIds :: Maybe [Int] -> Maybe [Int] -> Maybe [Int]
maybeCommonIds xs ys = intersect <$> xs <*> ys
intersect
is defined in Data.List
. Using maybeCommonIds
will result in Maybe [Int]
. The list contained inside will hold the common ids, but if either of the two lists did not exist, there is not list of common ids. In your case, this might be the same as having no common ids. In that case, you might want to pass the result to fromMaybe []
, to get back the list.
Upvotes: 0
Reputation: 742
To approach your problem from a different direction, I would argue you don't want a function that processes two Maybe [a]
. Bear with me:
Fundamentally, the operation you want to do operates on two lists to give you a new list, eg,
yourFunction :: [a] -> [a] -> [a]
yourFunction a b = ...
That's fine, and you can and should write yourFunction
as such. The fact that the data you have is Maybe [a]
captures some additional, auxiliary information: the operation that created your input lists may have failed. The next step is to chain together yourFunction
with the auxiliary information. This is exactly the purpose of do
notation, to mix pure operations (like yourFunction
) with contexts (the fact that the creation of one of your input lists may have failed):
playWithMaybe :: Maybe [a] -> Maybe [a] -> Maybe [a]
playWithMaybe maybeA maybeB =
do a <- maybeA -- if A is something, get it; otherwise, pass through Nothing
b <- maybeB -- if B is something, get it; otherwise, pass through Nothing
Just (yourFunction a b) -- both inputs succeeded! do the operation, and return the result
But then it turns out there are other kinds of contexts you might want to work with (a simple one, instead of Maybe
that just captures "something bad happened", we can use Either
to capture "something bad happened, and here is a description of what happened). Looking back at playWithMaybe
, the "Maybe
-ness" only shows up in one place, the Just
in the last line. It turns out Haskell offers a generic function pure
to wrap a pure value, like what we get from yourFunction
, in a minimal context:
playWithMaybe' :: Maybe [a] -> Maybe [a] -> Maybe [a]
playWithMaybe' maybeA maybeB =
do a <- maybeA
b <- maybeB
pure (yourFunction a b)
But then Haskell also has a generic type to abstract the idea of a context, the Monad. This lets us make our function even more generic:
playWithMonad :: Monad m => m [a] -> m [a] -> m [a]
playWithMonad mA mB =
do a <- mA
b <- mB
pure (yourFunction a b)
Now we have something very generic, and it turns out it is so generic, it's already in the standard library! (This is getting quite subtle, so don't worry if it doesn't all make sense yet.)
import Control.Applicative
play :: Monad m => m [a] -> m [a] -> m [a]
play mA mB = liftA2 yourFunction mA mB
or even
import Control.Applicative
play' :: Monad m => m [a] -> m [a] -> m [a]
play' = liftA2 yourFunction
Why did I switch from Monad to Applicative suddenly? Applicative is similar to Monad, but even more generic, so given the choice, it is generally better to use Applicative if you can (similar to my choice to use pure
instead of return
earlier). For a more complete explanation, I strongly recommend Learn You a Haskell (http://learnyouahaskell.com/chapters), in particular chapters 11 and 12. Note- definitely read chapter 11 first! Monads only makes sense after you have a grasp on Functor and Applicative.
Upvotes: 9
Reputation: 170745
In general:
yourFunction Nothing Nothing = ...
yourFunction (Just xs) Nothing =
case xs of
[] -> ...
x':xs' -> ...
-- or separately:
yourFunction (Just []) Nothing = ...
yourFunction (Just (x:xs)) Nothing = ...
et cetera. Which cases need to be treated separately depends on the specific function. More likely you would combine functions working on Maybe
with functions working on []
.
If you want to "Just return an list with no elements" for Nothing
, then you can write
maybeToList1 :: Maybe [a] -> [a]
maybeToList1 Nothing = []
maybeToList1 (Just xs) = xs
A better way to write the same function is maybeToList1 = maybe [] id
(docs for maybe
) or maybeToList1 = fromMaybe []
, but since you are just starting you may want to come back to this one later.
Upvotes: 5