blocoParaPicture :: Int -> [Picture] -> Picture
blocoParaPicture b l | b==0 = if elem pic1 l then pic1 else text "X"
| b==1 = if elem pic2 l then pic2 else text "X"
| otherwise = if elem pic3 l then pic3 else text "X"
I'm getting the error "Variable not in scope" to the pictures.
The expression elem x xs
checks if a given x
is in a list xs
. In your code when you write pic1
, there's no such variable in scope, it isn't defined anywhere. In any case you don't want to search for a specific value in the list, rather you want to know if a given position "exists", that is if the list is long enough.
Also you can't just "write" inside a function with this type. In Haskell input and output is reflected on the types. This is a pure function, that takes some arguments and calculates a result, no side effects.
So what you can do here is return a Maybe Picture
, which has values Nothing
or Just pic
depending whether you can return a picture or not. Or you can use Either String Picture
, where values are of the form Left string
or Right pic
. Lets go for this latter option.
blocoParaPicture :: Int -> [Picture] -> Either String Picture
In terms of implementation we could diverge from the subject to get into a discussion of error management (since the problem is that access to a position may fail). But at this point I think it's best to avoid that detour so lets keep it (relatively) simple.
direct recursion (simplest)
The simplest most direct method would be direct recursion (as suggested by @chepner in the comments below).
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture _ [] = Left "X"
blocoParaPicture 0 (x:_) = Right x
blocoParaPicture n (x:xs) = safe (n-1) xs
making sure !!
succeds
If you do want to use the standard access function !!
, one way to go about it (but potentially inefficient in the general case) would be to construct a "safe" infinite list.
import Data.List
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture n xs = zs !! n
where zs = [Right x | x <- xs] ++ repeat (Left "X")
The list zs
is an infinite list made up of two lists. First [Right x | x <- xs]
which just like your original list, but each element x
becomes Right x
. Then from then onwards all elements are of the form Left "X"
to indicate failure. In general the above approach can be inefficient. If you look for a big n
in a list:
[Right 1, Right 2] ++ [Left "X", Left "X", ...
you are doing many unnecessary steps, since you could stop when the first list ends. But works just fine for small n
.
using lookup
Yet another possibility, similar to your attempt to use the elem
function, would be to use lookup
on indices. This function is safe by design.
lookup :: Eq a => a -> [(a, b)] -> Maybe b
Following this approach you first construct the list,
[(0,x0), (1,x1), (2,x2) ...(k,xk)]
and then look for your given n
to return the associated xn
(or Nothing
).
blocoParaPicture' :: Int -> [Picture] -> Maybe Picture
blocoParaPicture' n xs = lookup n (zip [1..] xs)
This returns Nothing
when not found though. But if you wish to, you can convert to Either
via maybe :: b -> (a -> b) -> Maybe a -> b
.
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture n xs = maybe (Left "X") Right (lookup n (zip [1..] xs))
This is certainly a bit too complex when all you want is a simple access function. But can be handy in situations where things are not as simple.