[Haskell-beginners] Re: Beginners Digest, Vol 29, Issue 41

Jürgen Doser jurgen.doser at gmail.com
Fri Nov 26 15:51:19 EST 2010


El vie, 26-11-2010 a las 11:50 -0800, Russ Abbott escribió:
> [...]
> It's not accessing the data that concerns me, it's updating other
> elements.
> 
> Imagine a system that tracks student registration.
> 
> data Student = Student { sName :: String
>                                  {- ,  ... Other stuff -}
>                                  , coursesTaking :: [Course]
>                                  }
> 
> *
> data Rank = Assist | Assoc | Full
> 
> data Instructor = Student { iName :: String
>                                     , rank :: Rank               --
> defined,
> say, as   data Rank = Assist | Assoc | Full
> 
> {- ,  ... Other stuff -}
>                                     , coursesTeaching :: [Course]
>                                     }
> 
> data Course = Course { cName :: String
>                                , units :: Int
> 
>                               {- ,  ... Other stuff -}
>                                , instructor :: Instructor
>                                , enrolledStudents :: [Student]
>                                }
> 

In general, I try to avoid having cyclical data structures like this
(students pointing to courses, and courses pointing to students).
Functional programming languages are great for working with trees, not
necessarily general graphs. So I would try to find an acyclical
data-model for your problem. Your data-structure design looks more like
an object-oriented data-model. Basically, try to find the most
convenient "spanning tree" lying under your data-model. So you could
either remove the link from courses to students, or the link from
students to courses (or even both), and put the linking information
somewhere else, for example, into the Database structure you propose
below. Similar to what you would do if you would normalize an ER-schema
or SQL-schema for a Database.

>  Suppose I want a function that drops a student from a class.
> 
> drop :: (Course,  Student) ->
>  (Course,  Student)
> 
> I'm declaring the function as pair to pair because both change.
> 
> If I run drop, I get a new Course record and a new Student record.
> Doesn't
> that mean I have to change all the Student, Instructor, and Course
>  record
> s that refer to them, which also means I have to change all the Course
>  record
>  s that refer to them, etc.?
> 
> Wouldn't I have to write something like this?
> 
> data DataBase = DB { students    :: [Student]
>                             , instructors :: [Instructor]
>                             , courses     :: [Course]
>                             }
> 
I would probably do sth like this:

data DataBase = DB { students    :: [Student] -- Student has no coursesTaking field
                   , instructors :: [Instructor]
                   , courses     :: [Course]  -- Course has no enrolledStudent field
		   , student_courses :: [(Student,Course)] 
                   -- or even: 
                   -- student_courses :: [(Student_id, Course_id)]
                   }

Now, functions like 

> drop :: DB -> Course -> Student -> DB
 
are trivial.

Basically, when you find that you want to "change the state of the
world", it is a good idea to have "the world" available as a function
argument. Like in the DataBase datastructure above. Once you have that,
you can then play around: 

- how can I make certain aspects easier available in my "world"?

- maybe Map Student_id [Course_id] is better than 
  [(Student_id, Course_id)] ?

- or the other way round?

- maybe using the State Monad make the code easier:
  drop :: Course -> Student -> State DB ()

The object-oriented thinking: "I have a Student object, so I want to ask
*it* which courses it is taking" doesn't work too well, that's true.
Instead, ask "the system", "the world", "the student-course registry",
etc. what courses a particular student is taking.


	Jürgen



More information about the Beginners mailing list