0

I have the following function:

blockToPicture :: Int -> [Picture] -> Picture
blockToPicture n [pic1,pic2,pic3] | n==0 = ...
                                  | n==1 = ...
                                  | otherwise = ...

If n==0 I want to select pic1, if n==1 I want to select pic2. Otherwise I want to select pic3. The problem is when one of the pictures doesn't load, so it doesn't appear on the list. Instead of [pic1,pic2,pic3] I have something like [Pic1,Pic3]. When the function is supposed to select a picture that isn't on the list I want it to write "X" instead. For that I'll use the function text "X" instead. The problem is that I don't know how to make it write the "X" instead of selecting the wrong picture.

Edit: I've created the following function but for some reason I'm getting the error "Variable not in scope" to the pictures.

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"
  • Well here you could again pattern match on lists like `[pic1, pic2]`, `[pic1]` and `[]`, and each time replace cases that are not covered anymore. But I propose that you implement a recursive pattern where you recurse on a decremented index, and the tail of the list. – Willem Van Onsem Dec 08 '18 at 14:25
  • 2
    No, no, no, you should use the list of type `[Maybe Picture]` then. – Will Ness Dec 08 '18 at 14:46
  • What the text “X” function do? may use (Either String Picture) instead. – assembly.jc Dec 08 '18 at 14:47

2 Answers2

1

You can't just discard a picture that doesn't load; if you tried to load 3 pictures and end up with [some_pic, some_other_pic], how do you know which one didn't load? You need a list of type [Maybe Picture], with Just pic representing a successfully loaded picture and Nothing a failure. Then your function would look like

blockToPicture :: Int -> [Maybe Picture] -> Maybe Picture
blockToPicture _ []          = Nothing                  -- No pictures to choose from
blockToPicture 0 (Nothing:_) = Nothing                  -- Desired picture failed to load
blockToPicutre 0 (x:_)       = x                        -- Found desired picture!
blockToPicture n (_:xs)      = blockToPicture (n-1) xs  -- This isn't it; try the next one

Adapting Jorge Adriano's suggestion to use lookup (which is a good one)

import Control.Monad

blockToPicture :: Int -> [Maybe Picture] -> Maybe Picture
blockToPicture n pics = join (lookup n (zip [0..] pics))

Since lookup :: a -> [(a,b)] -> Maybe b and b here is Maybe Picture, we have a scenario where lookup returns Nothing if n is too big; Just Nothing if the desired picture fails to load, and Just (Just pic) if the desired picture is found. The join function from Control.Monad reduces the Maybe (Maybe Picture) value that lookup returns to the "regular" Maybe Picture that we want.

chepner
  • 497,756
  • 71
  • 530
  • 681
0
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.

Community
  • 1
  • 1
  • In the pathological case where `n` is *way* bigger then `length xs`, it would be more efficient to just check `n > length xs` than iterating over a large enough swath of the infinite list. – chepner Dec 08 '18 at 15:35
  • Or just write the recursion directly instead of using `!!`: `bPP _ [] = Left "X"; bPP 0 (x:_) = Right x; bPP n (x:xs) = bPP (n-1) xs`. – chepner Dec 08 '18 at 15:37
  • Indeed you're right, I considered that. In any case he's a beginner and supposedly looking at a list with just 3 elements... every approach teaches him something. – Jorge Adriano Branco Aires Dec 08 '18 at 16:03
  • 1
    Added your suggestion, perhaps that's best. Trying to stick with `!!` might have made it unnecessarily complicated. I also looked up a "safe" alternative to `!!` before but only found `Data.List.Safe`, but it uses the same name as the standard functions, and that would be confusing here. – Jorge Adriano Branco Aires Dec 08 '18 at 16:17