我来自OO语言,目前正在尝试在Haskell中开发应用程序。 我正在尝试使用OO语言中的抽象类,但这似乎不适合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
类来表达这个,并为每个转换创建一些实例,但是我对Parser感到困惑,因为它似乎不可能返回一个抽象类型。 / p>
这或多或少是我现在的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
但我不认为这是可能的,因为返回Maybe Resize
导致could not deduce a ~ Resize
,我可以理解。
我在这里遗漏了什么,或者我正在尝试在Haskell中做一个反模式。 如果这似乎是一个糟糕的方法,我很想知道在Haskell中处理这种情况的更好方法。
答案 0 :(得分:0)
感谢您的评论,我设法让应用程序正常运行, 所以我将回答我的问题,描述我的进展情况。
免责声明:这是我第一次写真人 在Haskell中的世界应用,所以我不确定所有这些都是最佳实践。
正如我在问题的评论中所说,我使用了一个函数,用于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
我根据ResizeInfo
解析了CropInfo
或parseProcessor
给出转换名称。
因此,在解析了所有转换后,我得到了Transformation
的列表
处理图像所需的一切,并保留一些信息
变换。
我得到的第二个问题是DI在Java中可以解决的问题。 这是一个简化的例子:
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中所做的不同, 它运作良好。
我使用了像这样定义的存储类
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
上面提到的两种模式我现在设法让事情变得干燥和可扩展。