Serializing Heterogenous Data
If you write enough programs it’s inevitable that you’ll realize at some point that you need to store some heterogenous collection of data. Doing this in Haskell isn’t hard, but the approach is non-obvious, and in learning how to manage these sorts of hetrogenous collections, you’ll also learn some more about how to better use Haskell’s type system.
In this chapter you’ll learn how to construct heterogenous collections of elements, and you’ll build your own hetrogeneous list type using existential types.
You’ll be able to work with heterogeneous collections and create data types with existential type variables.
In this chapter you’ll learn how to create an archive of data that you can serialize and store on disk using existential types. In the next chapter you’ll continue with this project by learning how to build tools to decode heterogenous data. In the following chapter you’ll learn about mutable data. The information you learn about existential data types in this chapter will be an important part of understanding the ideas behind some types of mutable data.
Nested FilePacks
Try building filepacks that contain other filepacks. Is there anything you can do to make the API more ergonomic? What do you notice about the API you’ve already built that makes this process easier or harder.
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise
Building A Real Archival Tool
Throughout this chapter you’ve built a library that will help you create a file archive. Try using this library, along with what you’ve learned over the last few chapters, to build a command line tool that will let you pack up files.
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise
Building A Trace Tool
In this chapter we focused on building a file archival tool, but many of the ideas you learned here are applicable to other domains as well. Another area you could use existential types is in building a tool to collect a trace of function calls.
Consider this example:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RecordWildCards #-}
module CallTrace where
import Text.Printf
data TraceData -- fill me in
newtype Trace -- fill me in
emptyTrace :: Trace
= undefined
emptyTrace
traceCall :: (Show a, Show b)
=> String
-> (a -> (Trace, b))
-> a
-> (Trace, b)
= undefined
traceCall
showTrace :: Trace -> String
= undefined
showTrace
factor :: Int -> (Trace, [Int])
=
factor n "factor" factor' (n, 2)
traceCall where
factor' :: (Int, Int) -> (Trace, [Int])
factor' (num, curFact)| num == 1 = (emptyTrace, [])
| (num `mod` curFact) == 0 =
let nextNumber = num `div` curFact
= "consFactor " <> show curFact
message = traceCall message factor' (nextNumber, curFact)
(trace, results) in (trace, curFact : results)
| otherwise =
let nextFactor = curFact + 1
in traceCall "skipFactor" factor' (num, nextFactor)
verboseFactor :: Int -> IO ()
= do
verboseFactor n let (trace, factors) = factor n
putStrLn "factors: "
print factors
putStrLn "trace: "
putStrLn (showTrace trace)
Try to implement the missing pieces so that your program compiles and gives you some appropriate output. Here are a couple of examples so that you can test your own code:
λ verboseFactor 3
factors:
[3]
trace:
stack depth: 3
factor (3,2) => [3]
skipFactor (3,3) => [3]
consFactor 3 (1,3) => []
λ verboseFactor 1080
factors:
[2,2,2,3,3,3,5]
trace:
stack depth: 11
factor (1080,2) => [2,2,2,3,3,3,5]
consFactor 2 (540,2) => [2,2,3,3,3,5]
consFactor 2 (270,2) => [2,3,3,3,5]
consFactor 2 (135,2) => [3,3,3,5]
skipFactor (135,3) => [3,3,3,5]
consFactor 3 (45,3) => [3,3,5]
consFactor 3 (15,3) => [3,5]
consFactor 3 (5,3) => [5]
skipFactor (5,4) => [5]
skipFactor (5,5) => [5]
consFactor 5 (1,5) => []
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise