Haskell 中面向对象抽象类型的模式
Pattern for OO abstract types in Haskell
我来自 OO 语言,目前正在尝试使用 Haskell 开发应用程序。
我正在尝试使用 OO 语言中的抽象 class,但这似乎不太适合 Haskell(可能还有大多数函数式语言)类型系统。
比方说 Java,我要表达的内容的最小代码可能看起来像这样
abstract class Transformation {
public abstract void transform(Image image);
}
class ResizeTransformation extends Transformation {
public void transform(Image image) {
// resize the image
}
}
// some other classes extending Transformation
class Worker {
public void applyTransformations(Image image, Transformation[] trs) {
for (Transformation t: trs) {
t.transform(image);
}
}
}
class Parser {
public Transformation parseTransformation(String rawTransformation) {
// return ResizeTransformation or some other concrete transformation
}
}
我试图在 Haskell 中用 Transformation
class 表达这一点,并为每个转换创建一些实例,但我被解析器卡住了,因为它似乎没有可以 return 抽象类型。
我的 Haskell 代码大致如下所示:
class Transformation a where
transform :: Image -> a -> Image
data Resize = Resize
{ newHeight :: Int
, newWidth :: Int
} deriving (Show)
instance Transformation Resize where
transform image resize = -- resize the image
applyTransformations :: (Transformation a) => Image -> [a] -> Image
applyTransformations image transformations = foldl transform image transformations
我想要一个带有这样签名的函数
parseTransformation :: (Transformation a) => String -> Maybe a
但我认为这是不可能的,因为 returning Maybe Resize
结果是 could not deduce a ~ Resize
,我可以理解。
我是不是遗漏了什么,或者我想在 Haskell 中做一个反模式。
如果这似乎是一种糟糕的方法,我很乐意在 Haskell.
中提示处理这种情况的更好方法。
感谢评论,我设法让应用程序正常工作,
所以我会回答我的问题,描述我是如何进行的。
免责声明:第一次写真实的
Haskell 中的 world 应用程序,所以我不确定所有这些都是最佳实践。
单一方法class/interface
正如我在问题的评论中所说,我使用了一个函数来实现 class 或 Java 中的接口。
abstract class Transformation {
public abstract void transform(Image image);
}
但是,考虑到我还需要有关转换的信息,
我使用了一条记录并在其中包含了一个函数,所以而不是
class Transformation a where
transform :: Image -> a -> Image
data Resize = Resize
{ newHeight :: Int
, newWidth :: Int
} deriving (Show)
instance Transformation Resize where
transform image resize = -- resize the image
我最终得到了类似
的结果
type Processor = Image -> Image
data Transformation = Transformation
{ name :: String
, args :: String
, processor :: Processor
}
parseTransformation :: String -> String -> Maybe Transformation
parseProcessor :: String -> String -> Maybe Processor
resizeProcessor :: ResizeInfo -> Processor
cropProcessor :: CropInfo -> Processor
我根据 parseProcessor
解析了 ResizeInfo
或 CropInfo
给定的转换名称。
所以在解析了所有的转换之后,我得到了一个 Transformation
的列表
处理图像和保存一些信息所需的一切
转换。
注入接口
我得到的第二个问题是 Java 中 DI 可以解决什么问题。
这是一个简化的例子:
public interface Storage {
public void load(StoredObject o);
public void save(StoredObject o);
}
public class S3Storage implements Storage {
// some implementation
}
public class LocalStorage implements Storage {
// some implementation
}
有了这个定义,我可以简单地实例化存储
取决于设置文件或其他文件,并将其注入
在我的申请的其余部分。
好吧,这实际上与我在 Haskell 中所做的并没有太大不同,
而且效果很好。
我使用了这样定义的存储class
data StoredObject = StoredObject
{ uniqueId :: String
, content :: Maybe ByteString
} deriving (Show)
class Storage a where
load :: a -> StoredObject -> IO (Either StorageError StoredObject)
save :: a -> StoredObject -> IO (Either StorageError ())
然后简单地创建了 LocalStorage
和 S3Storage
并为每个写了一个 Storage
实例。
data LocalStorage = LocalStorage
{ baseDir :: String
}
instance Storage LocalStorage where
save = -- implementation
load = -- implementation
我用这样的东西更改了我用来抽象已用存储的函数
downloadHandler :: (Storage a) => a -> IO ()
downloadHandler storage = -- process download
最后我简单解析了加载应用时的配置,
基于它创建了我想要的存储,并传递了它。
大致是这样的。
runApp :: String -> IO ()
runApp "local" = run makeLocalStorage
runApp "s3" = run makeS3Storage
runApp storage = putStrLn ("no storage named " ++ storage) >> exitFailure
run :: (Storage a) => a -> IO ()
run storage = -- run with my storage
通过上面的两个模式,我现在设法让事情变干并且具有足够的可扩展性。
我来自 OO 语言,目前正在尝试使用 Haskell 开发应用程序。 我正在尝试使用 OO 语言中的抽象 class,但这似乎不太适合 Haskell(可能还有大多数函数式语言)类型系统。
比方说 Java,我要表达的内容的最小代码可能看起来像这样
abstract class Transformation {
public abstract void transform(Image image);
}
class ResizeTransformation extends Transformation {
public void transform(Image image) {
// resize the image
}
}
// some other classes extending Transformation
class Worker {
public void applyTransformations(Image image, Transformation[] trs) {
for (Transformation t: trs) {
t.transform(image);
}
}
}
class Parser {
public Transformation parseTransformation(String rawTransformation) {
// return ResizeTransformation or some other concrete transformation
}
}
我试图在 Haskell 中用 Transformation
class 表达这一点,并为每个转换创建一些实例,但我被解析器卡住了,因为它似乎没有可以 return 抽象类型。
我的 Haskell 代码大致如下所示:
class Transformation a where
transform :: Image -> a -> Image
data Resize = Resize
{ newHeight :: Int
, newWidth :: Int
} deriving (Show)
instance Transformation Resize where
transform image resize = -- resize the image
applyTransformations :: (Transformation a) => Image -> [a] -> Image
applyTransformations image transformations = foldl transform image transformations
我想要一个带有这样签名的函数
parseTransformation :: (Transformation a) => String -> Maybe a
但我认为这是不可能的,因为 returning Maybe Resize
结果是 could not deduce a ~ Resize
,我可以理解。
我是不是遗漏了什么,或者我想在 Haskell 中做一个反模式。 如果这似乎是一种糟糕的方法,我很乐意在 Haskell.
中提示处理这种情况的更好方法。感谢评论,我设法让应用程序正常工作, 所以我会回答我的问题,描述我是如何进行的。
免责声明:第一次写真实的 Haskell 中的 world 应用程序,所以我不确定所有这些都是最佳实践。
单一方法class/interface
正如我在问题的评论中所说,我使用了一个函数来实现 class 或 Java 中的接口。
abstract class Transformation {
public abstract void transform(Image image);
}
但是,考虑到我还需要有关转换的信息, 我使用了一条记录并在其中包含了一个函数,所以而不是
class Transformation a where
transform :: Image -> a -> Image
data Resize = Resize
{ newHeight :: Int
, newWidth :: Int
} deriving (Show)
instance Transformation Resize where
transform image resize = -- resize the image
我最终得到了类似
的结果type Processor = Image -> Image
data Transformation = Transformation
{ name :: String
, args :: String
, processor :: Processor
}
parseTransformation :: String -> String -> Maybe Transformation
parseProcessor :: String -> String -> Maybe Processor
resizeProcessor :: ResizeInfo -> Processor
cropProcessor :: CropInfo -> Processor
我根据 parseProcessor
解析了 ResizeInfo
或 CropInfo
给定的转换名称。
所以在解析了所有的转换之后,我得到了一个 Transformation
的列表
处理图像和保存一些信息所需的一切
转换。
注入接口
我得到的第二个问题是 Java 中 DI 可以解决什么问题。 这是一个简化的例子:
public interface Storage {
public void load(StoredObject o);
public void save(StoredObject o);
}
public class S3Storage implements Storage {
// some implementation
}
public class LocalStorage implements Storage {
// some implementation
}
有了这个定义,我可以简单地实例化存储 取决于设置文件或其他文件,并将其注入 在我的申请的其余部分。
好吧,这实际上与我在 Haskell 中所做的并没有太大不同, 而且效果很好。
我使用了这样定义的存储class
data StoredObject = StoredObject
{ uniqueId :: String
, content :: Maybe ByteString
} deriving (Show)
class Storage a where
load :: a -> StoredObject -> IO (Either StorageError StoredObject)
save :: a -> StoredObject -> IO (Either StorageError ())
然后简单地创建了 LocalStorage
和 S3Storage
并为每个写了一个 Storage
实例。
data LocalStorage = LocalStorage
{ baseDir :: String
}
instance Storage LocalStorage where
save = -- implementation
load = -- implementation
我用这样的东西更改了我用来抽象已用存储的函数
downloadHandler :: (Storage a) => a -> IO ()
downloadHandler storage = -- process download
最后我简单解析了加载应用时的配置, 基于它创建了我想要的存储,并传递了它。 大致是这样的。
runApp :: String -> IO ()
runApp "local" = run makeLocalStorage
runApp "s3" = run makeS3Storage
runApp storage = putStrLn ("no storage named " ++ storage) >> exitFailure
run :: (Storage a) => a -> IO ()
run storage = -- run with my storage
通过上面的两个模式,我现在设法让事情变干并且具有足够的可扩展性。