joshj
joshj

Reputation: 583

What are the alternatives for accomplishing this same task?

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

Answers (3)

pat
pat

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

epsilonhalbe
epsilonhalbe

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

Joey Adams
Joey Adams

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

Related Questions