Reputation: 583
I have a structure that is instantiated in a parent function and I want to modify that instantiated data with calls to functions from that parent function. Here's a contrived example:
import Data.List
data MyLists = MyLists {
myInts :: [Int],
myBools :: [Bool]
} deriving (Show)
addIntToList :: Int -> MyLists -> MyLists
addIntToList x main_lists =
main_lists { myInts = Data.List.insert x my_ints }
-- might make a call to another child function that modifies main_list here, and so on (i.e., this is the vertical problem I see with this structuring)
where
my_ints = myInts main_lists
main :: IO()
main = do
let my_main_lists = MyLists [1,2,3] [False, True, False]
let my_new_main_lists = addIntToList 4 my_main_lists
print my_new_main_lists
let my_new_new_main_lists = addBoolToList True my_new_main_lists
print my_new_new_main_lists
-- and so on (this is the lateral problem I see with this code structuring)
What are the alternatives ways to structure this code or accomplish tasks similar to this? Is there a more concise way?
I should add that this gets particularly smelly (ie. code smell) once you're making a long chain of function calls to child functions; they all end up needing to return a new MyLists
or just returning main_list
without having done anything to it. That, and the parents might also have to deal with MyList
and another return value (e.g., -> (Bool, MyList)
).
So, you can imagine a tree structure of function calls all requiring a MyList parameter and return value; that doesn't seem optimal.
Here's a more concrete example of the kind of thing I'm talking about. Browse the code at https://github.com/mokehehe/monao (a super mario clone in haskell). You'll see that state.monad is never used, and that there are upper level structures that must flow throughout the code (e.g., GameGame in Main.hs).
Upvotes: 2
Views: 442
Reputation: 12749
You should look at Data.Lens
with a MonadState
:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.State
import Data.Lens
import Data.Lens.Template
import qualified Data.List
data MyLists = MyLists { _myInts :: [Int]
, _myBools :: [Bool]
} deriving ( Show )
$(makeLens ''MyLists)
addIntToList :: MonadState MyLists m => Int -> m [Int]
addIntToList i = myInts %= Data.List.insert i
addBoolToList :: MonadState MyLists m => Bool -> m [Bool]
addBoolToList b = myBools %= Data.List.insert b
program :: MonadState MyLists m => m ()
program = do
addIntToList 1
addBoolToList False
addIntToList 2
addBoolToList True
return ()
main = do
let ml = execState program MyLists { _myInts = []
, _myBools = []
}
print ml
Edit:
That's what I get for typing in untested code. I changed the example so it actually works! You need to have the data-lens
, data-lens-fd
, and data-lens-template
modules installed (use cabal install
).
Upvotes: 2
Reputation: 15967
I'm not a pro but this is how I would try to achieve this
import Data.List (insert)
data MyLists = MyLists { myInts :: [Int],
myBools :: [Bool]
} deriving (Show)
addIntToList :: Int -> MyLists -> MyLists
addIntToList i (MyLists is bs) = MyLists (insert i is) bs
-- if you don't care about order just do (i:is), which is faster
-- usually lists of i are denoted is (or sometimes I use ii) as a kind of plural
main :: IO()
main = do
let myMainList = MyLists [1,2,3] [False, True, False]
-- you would rather use CamelCase in Haskell than _
print $ addIntToList 4 myMainList
Upvotes: 1
Reputation: 43380
You can make it a little more concise by using the RecordWildCards extension:
{-# LANGUAGE RecordWildCards #-}
import Data.List (insert)
addIntToList :: Int -> MyLists -> MyLists
addIntToList x ml@MyLists{..} = ml{ myInts = insert x myInts }
The MyLists{..}
pattern dumps the properties of the MyLists
record into scope. Thus, we can easily reference the old myInts
when initializing the new myInts
.
Conversely, if you use ..
in expression context, it will fill in uninitialized properties with corresponding names in scope. The addIntToList
function can also be written as:
addIntToList x MyLists{..} = MyLists{ myInts = insert x myInts, .. }
One more cool thing you can do with record wildcards:
someAction = do
myInts <- getInts :: IO [Int]
myBools <- getBools :: IO [Bool]
return MyLists{..}
Also, Data.List.insert
is a bit verbose. You can just say insert
, since this import:
import Data.List
will import all of the names from Data.List
into the namespace. If you don't like this (e.g. because you want to define a function in your own module called insert
), you can import it qualified:
import qualified Data.List as List
… List.insert …
As far as using MyLists
in a program, the StateT
monad transformer is quite helpful:
{-# LANGUAGE RecordWildCards #-}
import Control.Monad.State
...
printList :: StateT MyLists IO ()
printList = liftIO . print =<< get
program :: StateT MyLists IO ()
program = do
printList
modify $ addIntToList 4
printList
modify $ addBoolToList True
printList
main :: IO()
main = evalStateT program $ MyLists [1,2,3] [False, True, False]
Upvotes: 3