Pierre P.
Pierre P.

Reputation: 1065

Operation on newly created data (update an array)

I've created the following data types.

{- Data declaration predefined in Haskell -}
data Maybe_ a = Nothing_ | Just_ a deriving( Show )

{- Array declaration -}
data Array_ a = A [ ( Int, Maybe_ a ) ] deriving( Show )

Now, I'd like to create an update function that - given an index, an Array_ and a new value - update the value of the array at the given index.

Here's the signature of the function ...

update :: ( Int, Array_ a, a ) -> Array_ a

... and here's the complete function

update :: ( Int, Array_ a, a ) -> Array_ a
update( i, A[], new_value ) = error( ( show i ) ++ " is not an index" )
update( i, A( ( j, Just_ x ):l ), new_value )
    | i == j = A( ( j, Just_ new_value ):l )
    | i /= j = update ( i, A l, new_value )

The issue occurs at the last line of the function. It's a recursive call to the end of the list, but it doesn't keep the previously consider elements of the array.

I was thinking of using either the ++ operator or the : one, but both of the time I get an error.

... i /= j = ( j, Just_ x ):update ( i, A l, new_value )

... i /= j = A[( j, Just_ x )] ++ update ( i, A l, new_value )

How can I handle this differently?

Upvotes: 0

Views: 52

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476503

First of all it is a bit un-Haskell to work with tuples, usually you define separate parameters, like:

update :: Int -> Array_ a -> a -> Array_ a

So that you can easily work with an update 5 function where you will later provide additional arguments.

Nevertheless the problem is that in your last branch:

| i /= j = update i (A l) new_value

you thus perform a recursive call, but the result of that recursive call will be the final result. In order to solve this issue, you simply prepend the item that you inspected, and did not match the index. Since you do this for all index failure, all indices that do not match will be in the final result, so:

| i /= j = (j, Just_ x) : update i (A l) new_value.

So now we obtain:

update :: Int -> Array_ a -> a -> Array_ a
update i (A []) new_value = error( ( show i ) ++ " is not an index" )
update i (A ((j, Just_ x):l)) new_value
    | i == j = A ((j, Just_ new_value):l)
    | i /= j = let A t = update i (A l) new_value in A ((j, Just_ x):t)

That being said there are still some things we can do to improve our function:

  • variables we do not take into account are usually written with a wildcard,
  • we can use an alias @ to prevent us from repacking the head of the list.
  • the compiler will probably error since you use == and /=. It is better to use otherwise (an alias of True in the last branch (this is more or less equivalent with else in Java).
  • there are some additional beautifications you can obtain with hlint.

If we take the above comments into account, an - in my opinion - more elegant function would be:

update :: Int -> Array_ a -> a -> Array_ a
update i (A []) _ = error $ show i ++ " is not an index"
update i (A (h@(j,Just_):l)) new_value
    | i == j = A ((j, Just_ new_value):l)
    | otherwise = let A t = update i (A l) new_value in A (h:t)

Finally there is still a case unresolved: what to do if there are Nothing_s in your list. If you don't care whether the value is a Just_ x or a Nothing_, you can rewrite it like:

update :: Int -> Array_ a -> a -> Array_ a
update i (A []) _ = error $ show i ++ " is not an index"
update i (A (h@(j,_):l)) new_value
    | i == j = A ((j, Just_ new_value):l)
    | otherwise = let A t = update i (A l) new_value in A (h:t)

Upvotes: 3

Related Questions