2014-10-07 4 views
7

Большая часть кода Haskell, который я вижу, использует прямые структуры, такие как списки и деревья. Например, он является общим для Haskeller написать:Каков эквивалент модулей OCaml в Haskell?

fillRect :: Color → Bounds → Image → Image 

Эта модель имеет проблему: если в дальнейшем программисте решает изменить определение «Image», или использовать другую структуру данных, то он будет необходимо рефактор каждый единый код используя. В OCaml вы можете просто использовать модуль, определяющий интерфейс к изображению, а затем принять решение о конкретных реализациях позже.

Что такое альтернатива Haskell для модулей OCaml?

+0

Как эта подпись типа означает, что 'fillRect' использует (или даже имеет доступ к) сведения о реализации типа' Image'? Как бы вы пишете эту подпись по-разному в OCaml (кроме очевидных синтаксических различий и разных соглашений об именах)? – sepp2k

+6

[Это аналог Haskell] (http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/) того, что вы описываете. Это не так сильно, как модули Ocaml (по крайней мере, не без большого количества расширений), но он охватывает наиболее распространенные случаи использования. Просто сделайте запись функций полиморфными по типу, который поддерживает эти операции. –

+2

Рюкзак - это попытка получить какую-то форму модуля в Haskell - http://plv.mpi-sws.org/backpack/ – nlucaroni

ответ

10

Существует несколько альтернатив; ни один из них точно не соответствует модулю ML, но каждый из них.

  • Параметрический полиморфизм. Вместо параметрирования вашего модуля и функции fillRect на нем Image вы задаете параметр Image -constructor на то, что указывает конкретный «вид» изображения. Так что бы подпись как

    fillRect_ppm :: ImageImplemetation i => Colour -> Bounds -> Image i -> Image i 
    

    ImageImplemetation где некоторый тип класса, который определяет то, как функции преобразования и/или серверными.
    С таким решением вы не можете действительно заменить тип Image полностью, но вы можете сделать этот тип произвольно гибким. Скорее всего, все конкретные типы, которые вы будете использовать для Image, действительно поделитесь некоторыми полями, поэтому лучше не делать его более гибким, чем необходимо: в разных экземплярах ImageImplemetation просто нужно реализовать то, что разных между ними. Если реализации очень похожи, возможно, вы хотите параметризовать только некоторые детали, такие как цветовое пространство.
    Конечно, вам нужно заранее спланировать это решение, но только для общего интерфейса. Вы можете поместить в любой тип в качестве аргумента позже, если вы определяете необходимые экземпляры.

  • Аналогичным образом (и возможно смешать), вы можете просто иметь класс типа для типов, которые могут хранить данные изображения.

    fillRect_tcl :: Image img => Colour -> Bounds -> img -> img 
    

    Image класс будет непосредственно определить некоторые примитивы, такие как, как рисовать линии, и fillRect_tcl бы использовать их в своей реализации. Хорошо, потому что fillRect_tcl работает сразу с любым типом. Что плохо, так это то, что класс Image должен быть довольно неловко большим (предпочитает использовать классы типов для «математического» материала с простыми и очень четкими законами).

    • Возможно, класс может даже прямоугольники как метод:

      class Image img where 
          ... 
          fillRect_tcm :: Colour -> Bounds -> img -> img 
          ... 
      

      не так хорошо, вы должны будете полностью заново определить этот метод для любого img экземпляра.

  • модульная система Haskell не может сделать много, но все-таки это иногда достаточно, чтобы спасти вас от рефакторинга все. Например, если Image пришел из модуля Data.Image, вы могли бы сделать

    import qualified Data.Image as IM 
    
    fillRect_qfi :: Colour -> Bounds -> IM.Image -> IM.Image 
    

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

Совершенно подходит к вашему примеру: diagrams использует как первые точки; например rect использует класс TrailLike для реализации своих примитивов, и этот класс включает в себя «окончательные» диаграммы типа QDiagram, который параметризуется на бэкэнде, который вы будете использовать для рендеринга.

Смежные вопросы