Say I have the following types:
type EndsTup = (Int,Int)
-- (0-based index from start or end, Frequency)
type FreqTup = (Char, [EndsTup], [EndsTup])
-- (Character, Freqs from start, Freqs from end)
type FreqData = [FreqTup]
-- 1 entry in the list for each letter
Which I’m using to store data regarding the frequency of a character’s position in a word. If I want to then save this to file, is it safe (as in guaranteed not to corrupt if it’s written without error) to convert the structure to a string using show, and then read it using something like:
readData <- readFile filePath
let reconstructed = read readData :: FreqData
I’m just asking because that seems “too easy”. Is this how it’s typically done?
1
It’s perfectly safe to do it this way, especially since, as Doval pointed out, all the instances of Show
and Read
that you’re using are in the standard library, and are thus guaranteed to cooperate with each other without any difficulty.
However, it’s not the generally recommended way to do it, simply because saving to human-readable (and Haskell-read
able) text is nowhere near as efficient as a tag-based binary format. For this, you can use the binary
package, which already includes instances for all of the datatypes that you’re using in the question.
More to the point, though, using the binary
package to serialize to and deserialize from disk is even easier then using Show
and Read
! (Just use the functions encodeFile
and decodeFile
(OrFail
) from the Data.Binary
module.) In general, you will have to get used to things in Haskell being much simpler, at least in the common cases, then they typically are in most imperative languages. 🙂
4
I haven’t tried it, but all signs point to “yes”, as long as you use the derived instances.
The Prelude has this to say about Show:
Derived instances of Show have the following properties, which are compatible with derived instances of Read:
- The result of show is a syntactically correct Haskell expression containing only constants, given the fixity declarations in force at the point where the type is declared. It contains only the constructor names defined in the data type, parentheses, and spaces. When labelled constructor fields are used, braces, commas, field names, and equal signs are also used.
- If the constructor is defined to be an infix operator, then showsPrec will produce infix applications of the constructor.
- the representation will be enclosed in parentheses if the precedence of the top-level constructor in x is less than d (associativity is ignored). Thus, if d is 0 then the result is never surrounded in parentheses; if d is 11 it is always surrounded in parentheses, unless it is an atomic expression.
- If the constructor is defined using record syntax, then show will produce the record-syntax form, with the fields given in the same order as the original declaration.
And it has this to say about Read:
Derived instances of Read make the following assumptions, which derived instances of Show obey:
- If the constructor is defined to be an infix operator, then the derived Read instance will parse only infix applications of the constructor (not the prefix form).
- Associativity is not used to reduce the occurrence of parentheses, although precedence may be.
- If the constructor is defined using record syntax, the derived Read will parse only the record-syntax form, and furthermore, the fields must be given in the same order as the original declaration.
- The derived Read instance allows arbitrary Haskell whitespace between tokens of the input string. Extra parentheses are also allowed.
Emphasis mine.
2