Search code examples
haskelllist-comprehension

How to fuse (kind of merge) some items in a list in Haskell


I have created the following three types in Haskell:

type Product = (Int, String, Float)

type Order = (Product, Int)

type Purchase = [Order]

The idea is that every Product has an ID (Int), a name (String) and a price (Float). An Order is a certain discrete quantity (Int) of a given Product. And a Purchase is a list of Orders.

With that, I have to create a function that takes two Purchases and then fuses them, returning another Purchase in return. For example, if an input is [(product1, 2), (product1, 3), (product2, 4), (product2, 5), (product3, 10)], then the output should be [(product1, 5), (product2, 9), (product3, 10)]. In other words, it removes all repeated Orders, sums all quantities, and outputs the resultant "fused" Purchase.

Here it is the function I currently have. It doesn't work, and Haskell points out several Couldn't match type errors.

fusePurchases :: Purchase -> Purchase -> Purchase
fusePurchases p1 p2 = let productsIds = nub [productId | (aproduct, _) <- p1 ++ p2, let productId = fst aproduct] in [(aproduct, sum [snd p | p <- p1 ++ p2, fst (fst p) == productId])
      | productId <- productsIds, let aproduct = head [prod | prod <- map fst (p1 ++ p2), fst prod == productId]]

What is wrong with it? What changes should be made? What would be an operating function?

Thanks!

I tried to create a single function and use list comprehension to merge the products into a list with unique elements in which the sum of all quantities of every Order is summed according to the other Orders with the same Product.


Solution

  • The first error:

    MergeOrders.hs:10:107-114: error:
        • Couldn't match type: (Int, String, Float)
                         with: (a1, b1)
          Expected: (a1, b1)
            Actual: Product
        • In the first argument of ‘fst’, namely ‘aproduct’
          In the expression: fst aproduct
          In an equation for ‘productId’: productId = fst aproduct
        • Relevant bindings include
            productId :: a1 (bound at MergeOrders.hs:10:91)
       |
    10 | fusionPurchases p1 p2 = let productsIds = nub [productId | (aproduct, _) <- p1 ++ p2, let productId = fst aproduct] in [(aproduct, sum [snd p | p <- p1 ++ p2, fst (fst p) == productId])
       |                                                                                                           ^^^^^^^^
    

    arises because aProduct has type Product, which is a triple (Int, String, Float), but you have tried to apply the function fst to it, and fst is only applicable to pairs. In Haskell, applying fst to a pair works:

    > fst (1,2)
    1
    

    but applying it to a triple does not:

    > fst (1,2,3)
    
    <interactive>:1:5: error:
        • Couldn't match expected type: (a, b0)
                      with actual type: (a0, b1, c0)
        • In the first argument of ‘fst’, namely ‘(1, 2, 3)’
          In the expression: fst (1, 2, 3)
          In an equation for ‘it’: it = fst (1, 2, 3)
        • Relevant bindings include it :: a (bound at <interactive>:1:1)
    

    The other two errors result from similar problems. In each case, you are trying to apply fst to a Product triple.

    You may find it helpful to define fst3:

    fst3 :: (a, b, c) -> a
    fst3 (a, _, _) = a
    

    With that definition, the following type checks okay:

    fusionPurchases :: Purchase -> Purchase -> Purchase
    fusionPurchases p1 p2 
      = let productsIds = nub [ productId | (aproduct, _) <- p1 ++ p2
                              , let productId = fst3 aproduct] 
        in [ (aproduct, sum [snd p | p <- p1 ++ p2, fst3 (fst p) == productId])
           | productId <- productsIds
           , let aproduct = head [ prod | prod <- map fst (p1 ++ p2)
                                 , fst3 prod == productId]]