Haskell中OO抽象类型的模式

时间:2015-07-12 08:40:25

标签: haskell design-patterns

我来自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中处理这种情况的更好方法。

1 个答案:

答案 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解析了CropInfoparseProcessor 给出转换名称。 因此,在解析了所有转换后,我得到了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 ())

然后简单地创建了LocalStorageS3Storage,并为每个实例编写了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

上面提到的两种模式我现在设法让事情变得干燥和可扩展。