Как совместить полиморфизм возвращаемого типа с параметрическим полиморфизмом в Haskell? - PullRequest
2 голосов
/ 13 июня 2019

У меня есть набор типов оболочек FilePath s (из-за ограничений используемой библиотеки, которые создают определенное хранилище на основе предоставленного типа) и несколько записей, которые мне нужно получить из этих путей к файлам.

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

Я хочу создать обобщенную функцию loadSource, которая принимает некоторые типы (фактически только обертки filepath) и на основе предоставленного типа создает значение другого определенного типа (Source, Header, Metadata, так далее.).Псевдокод:

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

эта функция не работает Я получаю несколько ошибок a’ is a rigid type variable bound by the type signature и rigid b...

, поэтому у меня нет нескольких подобных функций (код работает правильно):

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

Как мне этого добиться?

Ответы [ 2 ]

2 голосов
/ 14 июня 2019

Есть несколько способов сделать это, хотя, как говорит @DanielWagner, трудно сказать, что будет работать лучше для вас, без дополнительной информации о том, чего вы пытаетесь достичь.

Самым простым является, вероятно,используйте класс типов со связанным семейством типов (или многопараметрический класс типов с функциональной зависимостью), чтобы сопоставить тип оболочки файла с подтипом компилятора.Подход семейства типов выглядит следующим образом:

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

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

Обратите внимание, что здесь нет проблем с отображением двух оболочек путей к одному и тому же подтипу компилятора (например,type Load HeaderFilepath = Source будет работать нормально).

Дано:

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

определение loadSource:

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

, после чего:

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

Вы можете существенно уменьшить шаблон, параметризовав обертку, и - как @DanielWagner - я не понимаю ваш комментарий о том, что компилятор рассматривает их как файлы того же типа, поэтому вам нужно показать нам, чточто-то не так, когда вы пытаетесь это сделать.

В любом случае, мой полный источник исходного решения семейства типов:

{-# 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

и полный источник тегового решения:

{-# 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
1 голос
/ 13 июня 2019

Просто настройте свою обертку также:

newtype WrappedFilePath a = WrappedFilePath FilePath

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

Вы можете повторно использовать Tagged вместо создания нового WrappedFilePath, если хотите.

...