patternMinor
A simple database
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
```
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
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
To use
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.
Fortunately
Most of your DB-manipulating functions have twins in
Empty database can be represented as empty map.
Converting from
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.
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.DirectoryIt 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 TableDataPath type is redundant as Prelude already provides FilePath alias to String.-type Path = StringFortunately
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.insertEmpty database can be represented as empty map.
getTestDB :: Database
getTestDB =
- addTable "Third" . addTable "Second" $ addTable "First" []
+ addTable "Third" . addTable "Second" $ addTable "First" Map.emptyConverting 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.toListAlso 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.insertContext
StackExchange Code Review Q#60266, answer score: 2
Revisions (0)
No revisions yet.