Можно ли экспортировать конструкторы для сопоставления с образцом, но не для построения, в модулях Haskell? - PullRequest
27 голосов
/ 17 ноября 2011

Тип данных vanilla в Haskell имеет ноль или более конструкторов, каждый из которых играет две роли.

В выражениях он поддерживает введение, это функция с нуля или более аргументов для типа данных.

В шаблонах он поддерживает исключение, что-то вроде функции из типа данных в Maybe (кортеж типов аргументов).

Возможно ли, чтобы сигнатура модуля скрывала первое, открывая второе?

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

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

Следующая лучшая вещь - это скрыть конструкторы и создать набор функций из T -> Maybe (This, That)., T -> Возможно (Другое, Вещь) и т. Д.

Ответы [ 3 ]

37 голосов
/ 17 ноября 2011

Вы можете использовать тип представления и шаблоны просмотра , чтобы делать то, что вы хотите:

module ThingModule (Thing, ThingView(..), view) where

data Thing = Foo Thing | Bar Int

data ThingView = FooV Thing | BarV Int

view :: Thing -> ThingView
view (Foo x) = FooV x
view (Bar y) = BarV y

Обратите внимание, что ThingView не является рекурсивным типом данных: все конструкторы значений ссылаются на Thing.Так что теперь вы можете экспортировать конструкторы значений ThingView и сохранять Thing abstract.

Использовать так:

{-# LANGUAGE ViewPatterns #-}
module Main where

import ThingModule

doSomethingWithThing :: Thing -> Int
doSomethingWithThing(view -> FooV x) = doSomethingWithThing x
doSomethingWithThing(view -> BarV y) = y

Материал для обозначения стрелок - это GHC View Patterns .Обратите внимание, что для этого требуется языковая прагма.

Конечно, вам не нужно использовать шаблоны представлений, вы можете просто выполнить всю десагерацию вручную:

doSomethingWithThing :: Thing -> Int
doSomethingWithThing = doIt . view
  where doIt (FooV x) = doSomethingWithThing x
        doIt (BarV y) = y

Подробнее

На самом деле мы можем сделать немного лучше: нет причин дублировать все конструкторы значений для Thing и ThingView

module ThingModule (ThingView(..), Thing, view) where

   newtype Thing = T {view :: ThingView Thing}
   data ThingView a = Foo a | Bar Int

Продолжайте использовать его так же, как и раньше, но сейчасдля совпадений шаблонов можно использовать Foo и Bar.

{-# LANGUAGE ViewPatterns #-}
module Main where

import ThingModule

doSomethingWithThing :: Thing -> Int
doSomethingWithThing(view -> Foo x) = doSomethingWithThing x
doSomethingWithThing(view -> Bar y) = y
22 голосов
/ 06 августа 2014

Начиная с GHC 7.8, вы можете использовать PatternSynonyms для экспорта шаблонов независимо от конструкторов.Таким образом, аналогом ответа @ Lambdageek будет

{-# LANGUAGE PatternSynonyms #-}

module ThingModule (Thing, pattern Foo, pattern Bar) where

pattern Foo a <- RealFoo a
pattern Bar a <- RealBar a

data Thing = RealFoo Thing | RealBar Int

и

{-# LANGUAGE PatternSynonyms #-}
module Main where

import ThingModule

doSomethingWithThing :: Thing -> Int
doSomethingWithThing (Foo x) = doSomethingWithThing x
doSomethingWithThing (Bar y) = y

Так что это выглядит как обычные конструкторы.

Если вы попытаетесь использовать Bar дляпостроить значение, вы получите

Main.hs:9:32:
    Bar used in an expression, but it's a non-bidirectional pattern synonym
    In the expression: Bar y
8 голосов
/ 17 ноября 2011

Вы не можете. Но если для вашего типа T имеется только разумное количество конструкторов, вы можете скрыть конструкторы и вместо этого предоставить функцию, которая выполняет сопоставление с образцом в том же духе, что и maybe :: b -> (a -> b) -> Maybe a -> b.

...