Data.Map insert design

21 προβολές
Παράβλεψη και μετάβαση στο πρώτο μη αναγνωσμένο μήνυμα

David Jenkins

μη αναγνωσμένη,
24 Απρ 2015, 7:49:26 π.μ.24/4/15
ως haskel...@googlegroups.com
Dear Haskell experts,

I have been studying Haskell for about 8 months, motivated by its promise of (among other virtues) creating robust reliable code. I've been working through Mena's "Beginning Haskell" and O'Sullivan's "Real World Haskell", and while I'm slowly catching on I'm still very much a newbie. So if this forum is not the correct place for me to post this question, please let me know and I'll go elsewhere.

I'm a big fan of Eiffel, a language that also aims to facilitate the creation of reliable, reusable software, and the urge for me to compare Haskell to Eiffel is irresistible (and probably unfair). As you may already know, Eiffel offers syntactic support for what its creator, Bertrand Meyer, dubbed Design by Contract, something I find myself missing very often in Haskell. Let me offer an example:

Both Haskell and Eiffel offer data structures implementing hash tables--Haskell with Data.Map, Eiffel with its HASH_TABLE class. To add a new value to a Haskell Data.Map, "insert" is called; with the new key, value, and map supplied. While testing "insert", I discovered that if the key supplied to the function already exists in the supplied Map, then "insert" returns a new Map with the previously existing key's value changed to the new value. This surprised me--it means that "insert" not only adds a new key/value pair to a Map, but also updates an existing key/value pair, and, just by looking at the data types and signature for "insert", there's no way to tell which aspect of its behavior "insert" performed.

Eiffel does hash table insert a little differently. The {HASH_TABLE}.put procedure will also insert a new key/value pair, provided that the key does not already exist. "put" also changes its HASH_TABLE object's state to reflect the success or failure of its operation, and there are post-conditions written into "put", and that are considered essential to its interface, that reflect its design related to the success or failure of its operation. A careful Eiffel developer will read "put"'s interface before using it to determine what its behavior will be, and will check a HASH_TABLE object's state after "put" is called to ensure that it did what was expected.

I think I'm correct that one of Haskell's goals is to eliminate the sort of state change that Eiffel and other imperative languages rely for things like communicating the outcome of a routine, and that this is done to guarantee that a function called with the same arguments will return the same result every time. How then, though, would I, as a careful Haskell developer, make sure that I didn't unintentionally overwrite a value in a Map by calling "insert" with an existing key? Is it common practice to call Map's "member" function first to check for the existence of a key before calling "insert"? Should I be expected to read the Hackage documentation on "insert", that clearly describes "insert"'s dual behavior, before using it? How do real skillful Haskell developers handle a situation like inserting into a hash table? 

I want to add that I wouldn't have spent the last 8 months struggling through Haskell (it's really a lot different from anything I've used before) if I didn't think it offered many advantages.

David Jenkins

Martin Tang

μη αναγνωσμένη,
25 Απρ 2015, 9:44:37 μ.μ.25/4/15
ως haskel...@googlegroups.com
On Friday, April 24, 2015 at 7:49:26 AM UTC-4, David Jenkins wrote:
While testing "insert", I discovered that if the key supplied to the function already exists in the supplied Map, then "insert" returns a new Map with the previously existing key's value changed to the new value. This surprised me--it means that "insert" not only adds a new key/value pair to a Map, but also updates an existing key/value pair, and, just by looking at the data types and signature for "insert", there's no way to tell which aspect of its behavior "insert" performed.
[...]
How then, though, would I, as a careful Haskell developer, make sure that I didn't unintentionally overwrite a value in a Map by calling "insert" with an existing key? Is it common practice to call Map's "member" function first to check for the existence of a key before calling "insert"? Should I be expected to read the Hackage documentation on "insert", that clearly describes "insert"'s dual behavior, before using it?

There are multiple insert functions for different use cases: https://hackage.haskell.org/package/containers-0.5.6.3/docs/Data-Map-Lazy.html#g:6

In particular, insertLookupWithKey will let you know if the key was previously there. Also, since Data.Map is immutable, you don't lose the old key/val. It's still there as long as you can find it.

[You can also use assertions for pre/post-conditions, but I assume that's a pretty poor imitation of Eiffel's DbC capabilities. Most Haskellers would instead design their data types with smart constructors that refuse to construct malformed values.]
Απάντηση σε όλους
Απάντηση στον συντάκτη
Προώθηση
0 νέα μηνύματα