2 votes

Comment combiner le polymorphisme de type de retour avec le polymorphisme paramétrique en Haskell ?

J'ai un ensemble de types de wrapper FilePath (en raison des restrictions de la bibliothèque que j'utilise, qui crée un stockage spécifique basé sur le type fourni) et quelques enregistrements que je dois obtenir de ces chemins de fichiers.

newtype SourceFilepath = SourceFilepath String deriving (Show)
newtype HeaderFilepath = HeaderFilepath String deriving (Show)
-- ..many more wrappers

data Source =
  Source {..}

data Header = 
  Header {..}

data Metadata = 
  Metadata {..}

-- .. many more record types

Je veux créer une fonction généralisée loadSource qui accepte certains types (en fait, uniquement des wrappers de chemin de fichier) et, en fonction du type fourni, produit une valeur d'un autre type spécifique ( Source , Header , Metadata etc.). Pseudocode :

loadSource :: a -> Compiler b
loadSource (SourceFilepath path) = subload path
loadSource (HeaderFilepath path) = subload path
-- .. other cases for other types 
--
-- `a` can be filepath wrappers
-- different `a` can lead to the same `b` sometimes

cette fonction n'est pas opérationnelle, je reçois plusieurs a’ is a rigid type variable bound by the type signature y rigid b.. erreurs.

pour ne pas avoir plusieurs fonctions comme celle-ci (le code fonctionne correctement) :

subload :: FromJSON b => FilePath -> Compiler b
subload path = <already implemented operational logic>

loadHeader :: HeaderFilepath -> Comiler Header
loadHeader (HeaderPath path) = subload path

loadMetadata :: MetadataFilepath -> Comiler Metadata
loadMetadata (MetadataFilepath path) = subload path

-- .. many more similar functions

Comment puis-je y parvenir ?

2voto

K. A. Buhr Points 14622

Il existe plusieurs façons d'y parvenir, mais comme le dit @DanielWagner, il est difficile de dire ce qui fonctionnera le mieux pour vous sans plus de détails sur ce que vous essayez d'obtenir.

Le plus simple est probablement d'utiliser une classe de types avec une famille de types associée (ou une classe de types multiparamètres avec une dépendance fonctionnelle) pour faire correspondre le type du wrapper de chemin de fichier au sous-type du compilateur. L'approche par famille de types ressemble à ceci :

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}

class Loadable a where
  filepath :: a -> String
  type Load a

avec des instances de type "boilerplatey" :

instance Loadable SourceFilepath where
  filepath (SourceFilepath pth) = pth
  type Load SourceFilepath = Source
instance Loadable HeaderFilepath where
  filepath (HeaderFilepath pth) = pth
  type Load HeaderFilepath = Header
instance Loadable MetadataFilepath where
  filepath (MetadataFilepath pth) = pth
  type Load MetadataFilepath = Metadata

Notez qu'il n'y a aucun problème ici à faire correspondre deux wrappers de chemin de fichier au même sous-type de compilateur (par ex, type Load HeaderFilepath = Source fonctionnerait parfaitement).

Étant donné :

subload :: FromJSON b => FilePath -> Compiler b
subload = ...

la définition de loadSource est :

loadSource :: (Loadable a, FromJSON (Load a)) => a -> Compiler (Load a)
loadSource = subload . filepath

après quoi :

> :t loadSource (SourceFilepath "bob")
loadSource (SourceFilepath "bob") :: Compiler Source
> :t loadSource (MetadataFilepath "alice")
loadSource (MetadataFilepath "alice") :: Compiler Metadata

Vous pouvez réduire considérablement le boilerplate en paramétrant le wrapper, et -- comme @DanielWagner -- je ne comprends pas votre commentaire sur le fait que le compilateur les traite comme le même type de fichier, donc vous devrez nous montrer ce qui ne va pas quand vous essayez cela.

Quoi qu'il en soit, ma source complète pour la solution familiale de type original :

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}

import Data.Aeson
import GHC.Generics

newtype SourceFilepath = SourceFilepath String deriving (Show)
newtype HeaderFilepath = HeaderFilepath String deriving (Show)
newtype MetadataFilepath = MetadataFilepath String deriving (Show)

data Source = Source deriving (Generic)
data Header = Header deriving (Generic)
data Metadata = Metadata deriving (Generic)

instance FromJSON Source
instance FromJSON Header
instance FromJSON Metadata

data Compiler b = Compiler

subload :: FromJSON b => FilePath -> Compiler b
subload = undefined

class Loadable a where
  filepath :: a -> String
  type Load a
instance Loadable SourceFilepath where
  filepath (SourceFilepath pth) = pth
  type Load SourceFilepath = Source
instance Loadable HeaderFilepath where
  filepath (HeaderFilepath pth) = pth
  type Load HeaderFilepath = Header
instance Loadable MetadataFilepath where
  filepath (MetadataFilepath pth) = pth
  type Load MetadataFilepath = Metadata

loadSource :: (Loadable a, FromJSON (Load a)) => a -> Compiler (Load a)
loadSource = subload . filepath

et la source complète pour une solution étiquetée :

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}

import Data.Aeson
import GHC.Generics

newtype TypedFilePath a = TypedFilePath FilePath deriving (Show)

data Source = Source deriving (Generic)
data Header = Header deriving (Generic)
data Metadata = Metadata deriving (Generic)

instance FromJSON Source
instance FromJSON Header
instance FromJSON Metadata

data Compiler b = Compiler

subload :: FromJSON b => FilePath -> Compiler b
subload = undefined

type family Load a where
  Load Source = Source
  Load Header = Header
  Load Metadata = Metadata

loadSource :: FromJSON (Load a) => TypedFilePath a -> Compiler (Load a)
loadSource (TypedFilePath fn) = subload fn

1voto

Daniel Wagner Points 38831

Il suffit que votre wrapper soit paramétré lui aussi :

newtype WrappedFilePath a = WrappedFilePath FilePath

loadSource :: FromJSON a => WrappedFilePath a -> Compiler a
loadSource (WrappedFilePath p) = subload fp

Vous pouvez réutiliser Tagged au lieu de créer le nouveau WrappedFilePath si vous aimez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X