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

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

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

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

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

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

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

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

3 ответа

Решение

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

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 Аннотация.

Используйте как это:

{-# LANGUAGE ViewPatterns #-}
module Main where

import ThingModule

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

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

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

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

Начиная с 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

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

Другие вопросы по тегам