From my (admittedly limited) exposure to functional programming languages, such as Clojure, it seems that encapsulation of data has a less important role. Usually various native types such as maps or sets are the preferred currency of representing data, over objects. Furthermore, that data is generally immutable.
For example, here's one of the more famous quotes from Rich Hickey of Clojure fame, in an interview about the matter:
Fogus: Following that idea—some people are surprised by the fact that Clojure does not engage in data-hiding encapsulation on its types. Why did you decide to forgo data-hiding?
Hickey: Let’s be clear that Clojure strongly emphasizes programming to abstractions. At some point though, someone is going to need to have access to the data. And if you have a notion of “private”, you need corresponding notions of privilege and trust. And that adds a whole ton of complexity and little value, creates rigidity in a system, and often forces things to live in places they shouldn’t. This is in addition to the other losing that occurs when simple information is put into classes. To the extent the data is immutable, there is little harm that can come of providing access, other than that someone could come to depend upon something that might change. Well, okay, people do that all the time in real life, and when things change, they adapt. And if they are rational, they know when they make a decision based upon something that can change that they might in the future need to adapt. So, it’s a risk management decision, one I think programmers should be free to make. If people don’t have the sensibilities to desire to program to abstractions and to be wary of marrying implementation details, then they are never going to be good programmers.
Coming from the OO world, this seems to complicate some of the enshrined principles I've learned over the years. These include Information Hiding, the Law of Demeter and Uniform Access Principle, to name a few. The common thread being that encapsulation allows us to define an API for others to know what they should and shouldn't touch. In essence, creating a contract that allows for the maintainer of some code to freely make changes and refactorings without worrying about how it might introduce bugs into the consumer's code (Open/Closed principle). It also provides a clean, curated interface for other programmers to know which tools they can use to get at or build upon that data.
When the data is allowed to be directly accessed, that API contract is broken and all those encapsulation benefits seem to go away. Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.
How do functional codebases address these issues that seem to come up when the size of a codebase grows enormous such that APIs need to be defined and lots of developers are involved on working with specific parts of the system? Are there examples of this situation available that demonstrate how this is handled in these type of codebases?
Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.
Not really. The only thing that changes is that the changes end up on a new object. This is a huge win when it comes to reasoning about the code; passing mutable objects around means having to keep track of who might mutate them, a problem which scales up with the size of the code.