
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