Non sono sicuro di come rispondere a questa domanda. Diciamo che sto provando a passare i percorsi dei file tmp, e voglio catturare l'idea che ci sono diversi formati di tmpfile, e ogni funzione funziona solo su uno di essi. Questo funziona:In Haskell, come si restringono le funzioni a un solo costruttore di un tipo di dati?
data FileFormat
= Spreadsheet
| Picture
| Video
deriving Show
data TmpFile = TmpFile FileFormat FilePath
deriving Show
videoPath :: TmpFile -> FilePath
videoPath (TmpFile Video p) = p
videoPath _ = error "only works on videos!"
Ma ci deve essere un modo migliore di scriverlo senza errori di runtime giusto? Ho pensato a due alternative, questo:
type TmpSpreadsheet = TmpFile Spreadsheet
type TmpPicture = TmpFile Picture
type TmpVideo = TmpFile Video
videoPath :: TmpVideo -> FilePath
o questo:
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
Ma ovviamente non compilazione. Qual è il modo giusto per farlo? Alcune altre idee, nessuno particolarmente interessante:
- Wrap
TmpFile
nel formato invece che il contrario, per cui i valori sonoVideo (TmpFile "test.avi")
ecc - fare un sacco di tipi di dati separati
VideoTmpFile
,PictureTmpFile
ecc - Fare un typeclass
TmpFile
- Utilizzare le funzioni parziali ovunque, ma aggiungere funzioni di guardia di astrarre il pattern matching
Ho anche preso in considerazione l'apprendimento dell'estensione -XDataKinds
, ma sospetto che manchi qualcosa di molto più semplice che si può fare senza di esso.
EDIT: Sto imparando molto oggi! Ho provato entrambi gli approcci descritti di seguito (DataKinds
e tipi di fantasmi, che hanno costruttori di valori fittizi che possono essere rimossi con un'altra estensione), ed entrambi funzionano! Poi ho provato ad andare un po 'oltre. Entrambi ti permettono di creare un tipo annidato TmpFile (ListOf a)
oltre al normale TmpFile a
, che è bello. Ma ho deciso di andare con un tipo di fantasma puro (costruttori di valore intatto), perché è possibile creare un modello di corrispondenza su di essi. Ad esempio, sono rimasto sorpreso che questo in realtà funziona:
data Spreadsheet = Spreadsheet deriving Show
data Picture = Picture deriving Show
data Video = Video deriving Show
data ListOf a = ListOf a deriving Show
data TmpFile a = TmpFile a FilePath
deriving Show
videoPath :: TmpFile Video -> FilePath
videoPath (TmpFile Video p) = p
-- read a file that contains a list of filenames of type a,
-- and return them as individual typed tmpfiles
listFiles :: TmpFile (ListOf a) -> IO [TmpFile a]
listFiles (TmpFile (ListOf fmt) path) = do
txt <- readFile path
let paths = map (TmpFile fmt) (lines txt)
return paths
vidPath :: TmpFile Video
vidPath = TmpFile Video "video1.txt"
-- $ cat videos.txt
-- video1.avi
-- video2.avi
vidsList :: TmpFile (ListOf Video)
vidsList = TmpFile (ListOf Video) "videos.txt"
main :: IO [FilePath]
main = do
paths <- listFiles vidsList -- [TmpFile Video "video1.avi",TmpFile Video "video2.avi"]
return $ map videoPath paths -- ["video1.avi","video2.avi"]
Per quanto posso dire, l'equivalente con DataKinds
è molto simile, ma non può accedere fmt
come un valore:
{-# LANGUAGE DataKinds, KindSignatures #-}
data FileFormat
= Spreadsheet
| Picture
| Video
| ListOf FileFormat
deriving Show
data TmpFile (a :: FileFormat) = TmpFile FilePath
deriving Show
vidPath :: TmpFile Video
vidPath = TmpFile "video.avi"
vidsList :: TmpFile (ListOf Video)
vidsList = TmpFile "videos.txt"
videoPath :: TmpFile Video -> FilePath
videoPath (TmpFile p) = p
listFiles :: TmpFile (ListOf a) -> IO [TmpFile a]
listFiles (TmpFile path) = do
txt <- readFile path
let paths = map TmpFile (lines txt)
return paths
main :: IO [FilePath]
main = do
paths <- listFiles vidsList
return $ map videoPath paths
(Può sembrare una cosa strana da volere, ma il mio programma effettivo sarà un interprete per un linguaggio piccolo che compila le regole Shake con un file tmp corrispondente a ciascuna variabile, quindi saranno utili gli elenchi digitati di tmpfile)
Sembra destra? Mi piace l'idea di DataKinds
meglio, quindi ci andrei invece se potessi esaminarli come valori, o se risultasse che non è mai necessario.
dipende un po 'da quello che vuoi fare - ad esempio per quello che ho visto dalla tua domanda sceglierei in effetti semplici (separati) wrapper 'newtype' come' newtype VideoPath = VideoPath FilePath' – Carsten
o prendi il * tipo phantom * approccio: 'data tempfile a = tempfile FilePath' con' dati Videoformat' (GHC ha bisogno di estensione 'EmptyDataDecls') e poi lasciare che' videofile = tempfile myPath :: tempfile Videoformat' ... – Carsten