HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

A simple database

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
databasesimplestackoverflow

Problem

After failing miserably to get SQL to work with Haskell, I decided to try making my own system. It's fairly simple, but works well for simple jobs (I made it to handle highscores for a number guessing game). Its biggest issue is that the "tables" are only 1-dimensional currently, so it's quite limited in that regard.

Besides the obvious issue of it only being 1-dimensional, I'm looking for general feedback. Anything related to good/bad practices would be appreciated.

I'm also wondering about the tableIsInDB function. I need to do the pre-check for an empty list being passed to it (because if any receives an empty list, it will always output true), but that forces the second definition to have 2 parameters (otherwise I get a "functions definitions have a different number of arguments" error), which prevents it from being written point-free. This isn't a big deal, but I've noticed that functions being written point-free are generally preffered. Is there any way of writing it point-free if it has 2 definitions?

```
import Data.Binary
import Data.List
import System.Directory

type TableDatum = String
type TableName = String
type TableData = [TableDatum]
type Table = (TableName,TableData)
type Database = [Table]

type Path = String

loadDB :: Path -> IO Database
loadDB path = do
fileExists Database -> IO ()
saveDB path db = do
let tempPath = (path ++ "Temp")
encodeFile tempPath db
renameFile tempPath path

tableName :: Table -> TableName
tableName = fst

tableData :: Table -> TableData
tableData = snd

tableIsInDB :: TableName -> Database -> Bool
tableIsInDB _ [] = False
tableIsInDB name db = any (== True) $ map (\table ->
tableName table == name) db

getTableNames :: Database -> [TableName]
getTableNames = map fst

addTable :: TableName -> Database -> Database
addTable name = ((name,[]):)

addData :: TableDatum -> Table -> Table
addData dat (tName,tDat) = (tName,dat:tDat)

addDataTo :: TableName -> TableDatum -> Database -> Database
ad

Solution

I suggest you to use more advanced data structures than plain list. As your Database is actually a map from TableName to TableData it is better to represent it as Map.

To use Map we need some imports (please be sure to import it qualified, otherwise some names may clash).

import Data.Binary
 import Data.List
+import Data.Map (Map)
+import qualified Data.Map as Map
 import System.Directory


It is really nice that you provided domain-specific type aliases. This makes type signatures more readable and allows to easily change underlying data representation as in this case.

-type Database   = [Table]
+type Database   = Map TableName TableData


Path type is redundant as Prelude already provides FilePath alias to String.

-type Path = String


Fortunately Data.Map is instance of Binary, so loadDB and saveDB need only small changes.

-loadDB :: Path -> IO Database
+loadDB :: FilePath -> IO Database
 loadDB path = do
     fileExists  Database -> IO ()
+saveDB :: FilePath -> Database -> IO ()


Most of your DB-manipulating functions have twins in Data.Map module:

tableIsInDB :: TableName -> Database -> Bool
 tableIsInDB = Map.member

 getTableNames :: Database -> [TableName]
 getTableNames = Map.keys

 addTable :: TableName -> Database -> Database
 addTable name = Map.insert name []

 addDataTo :: TableName -> TableDatum -> Database -> Database
 addDataTo name dat = Map.insertWith (++) name [dat]

 getTable :: TableName -> Database -> Table
 getTable name db = case Map.lookup name db of
   Nothing  -> ("",[])
   Just dat -> (name, dat)

 overwriteTable :: Table -> Database -> Database
 overwriteTable = uncurry Map.insert


Empty database can be represented as empty map.

getTestDB :: Database
 getTestDB = 
-    addTable "Third" . addTable "Second" $ addTable "First" []
+    addTable "Third" . addTable "Second" $ addTable "First" Map.empty


Converting from Map database to the old list-based is easy with Map.toList:

showDB :: Database -> String
-showDB db = concatMap showTable db
+showDB = concatMap showTable . Map.toList


Also have to note that I like your coding style: domain-specific type aliases and short functions with descriptive names make code easy to read.

Code Snippets

import Data.Binary
 import Data.List
+import Data.Map (Map)
+import qualified Data.Map as Map
 import System.Directory
-type Database   = [Table]
+type Database   = Map TableName TableData
-type Path = String
-loadDB :: Path -> IO Database
+loadDB :: FilePath -> IO Database
 loadDB path = do
     fileExists <- doesFileExist path
     if fileExists
         then decodeFile path
-    else return []
+    else return Map.empty

-saveDB :: Path -> Database -> IO ()
+saveDB :: FilePath -> Database -> IO ()
tableIsInDB :: TableName -> Database -> Bool
 tableIsInDB = Map.member

 getTableNames :: Database -> [TableName]
 getTableNames = Map.keys

 addTable :: TableName -> Database -> Database
 addTable name = Map.insert name []

 addDataTo :: TableName -> TableDatum -> Database -> Database
 addDataTo name dat = Map.insertWith (++) name [dat]

 getTable :: TableName -> Database -> Table
 getTable name db = case Map.lookup name db of
   Nothing  -> ("",[])
   Just dat -> (name, dat)

 overwriteTable :: Table -> Database -> Database
 overwriteTable = uncurry Map.insert

Context

StackExchange Code Review Q#60266, answer score: 2

Revisions (0)

No revisions yet.