DevNebulae
DevNebulae

Reputation: 4906

Manipulating single item in list

import Control.Lens
import Data.List (filter)
import Data.Time
import Data.Text

data Vehicle = Vehicle
  { _name :: Text
  , _admissionDate :: Day
  , _vehicleId :: Int
  , _refuels :: [Refuel]
  }

makeLenses ''Vehicle

data Refuel = Refuel
  { distance :: Int
  , volume :: Double
  , pricePerUnit :: Double
  }

addRefuel :: Refuel -> Vehicle -> Vehicle
addRefuel refuel = refuels <>~ [refuel]

addRefuelToVehicle :: Int -> refuel -> [Vehicle] -> [Vehicle]
addRefuelToVehicle vehicleId refuel vehicles =
  case vehicle of
    -- Not actual implementation
    Nothing -> error "Vehicle not found"
    Just v ->
      let
        vehicle = addRefuel v refuel
      -- Add everything that is not the vehicle and add modified vehicle
      filter (\v' -> v' ˆ. vehicleId /= vehicleId) vehicles ++ [vehicle]
  where
    vehicle = find (\v -> v ^. vehicleId == vehicleId) vehicles

vehicles =
  [ Vehicle { _name = "Car 1", _admissionDate = fromGregorian 2006 4 6, _vehicleId = 1, _refuels = []
  , Vehicle { _name = "Car 2", _admissionDate = fromGregorian 2013 7 22, _vehicleId = 2, _refuels = []
  ]

In this example I want to add a Refuel to the _refuels list of the vehicle with _vehicleId = 1. In an object-oriented language, I would solve it like this:

// Ignoring the fact that it may be null
vehicles
  .find { it._vehicleId == 1 }
  ._refuels
  .append(refuel)

How would you go around and solve this in Haskell?

Upvotes: 1

Views: 65

Answers (2)

chepner
chepner

Reputation: 531165

Without using lenses, you would write a function like

addRefuel :: ID  -- vehicle id
          -> Refuel  -- refueling data
          -> [Vehicle] -- list of vehicles, one of which you want to update
          -> [Vehicle] -- list of vehicles after the update
addRefuel _ _ [] = []
addRefuel vid r (v:vs) | _vehicleId v == vid = v { _refuel = r : _refuel v} : vs
                       | otherwise = v : addRefuel vid r vs

You can call this on your vehicles list, but then you pass the return value to whatever code will use your updated list.

Upvotes: 1

leftaroundabout
leftaroundabout

Reputation: 120711

You can do it very similar to your OO code:

vehicles & traverse
         . filtered ((==1) . _vehicleId)
         . refuels
         <>~ [refuel]

N.B.: this will update all vehicles with matching IDs, and if there's none it'll just silently do nothing. Maybe not the behavious you want.

Upvotes: 3

Related Questions