Как получить тип с индексируемыми, но необязательными элементами, требующими наличия в Haskell? - PullRequest
2 голосов
/ 02 февраля 2011

Мне нужна структура типов, которая имеет определенное количество «слотов», которые индексируются (чтобы мы могли реагировать на элементы в слоте 1 или 2 или 3 по отдельности и последовательно)

(Может быть, Возможно, b, Может быть, c ...) громоздко, трудно для фреймворка делать много, и позволяет представление (Nothing, Nothing ...), которое не должно быть разрешено для того, что я делаю.

Thisработает: данные Or ab = OrBoth ab |OrLeft a |OrRight b

и имеет точно правильную семантику, но это совпадение с шаблоном.

OrBoth tas1 (OrRight (OrRight new)) ->
OrBoth tas1 (OrRight (OrBoth _ new)) ->
OrBoth tas1 (OrBoth _ (OrRight new)) ->
OrBoth tas1 (OrBoth _ (OrBoth _ new)) ->

Любые другие идеи о том, как это можно сделать эффективно и читабельно?

Ответ Эдьки великолепен, и у меня есть еще один вопрос:

Можно ли заставить его создать "кортеж" нужного размера?

step :: (Nothingable a, Nothingable b) => SignalFunction a b -> a -> (SignalFunction a b, b)
step sf nothing = (sf, nothing) -- second nothing here is error
step sf a = transition sf a

src/Processors.hs:59:23:
    Couldn't match expected type `b' against inferred type `a'
      `b' is a rigid type variable bound by
          the type signature for `step' at src/Processors.hs:58:36
      `a' is a rigid type variable bound by
          the type signature for `step' at src/Processors.hs:58:21
    In the expression: nothing

Ответы [ 6 ]

2 голосов
/ 03 февраля 2011

GADT-ы могут быть полезны для этого типа вещей.Не уверен, насколько это практично, но вы можете сопоставить с шаблоном, и это не позволит вам пройти «пустой» (все «None») случай.Будучи «гетерогенной коллекцией» Spec может иметь произвольную длину и может указывать элементы разных типов (кортежоподобных).

{-# LANGUAGE TypeOperators, EmptyDataDecls, GADTs, TypeFamilies #-}

data Empty
data NonEmpty

-- Infix forms of data type and constructor (looks nicer here)
infixr 7 :*:
data a :*: b

-- GADT definition of heterogeneous list
-- with 'e' parameter specifing possible "emptiness" of the result (if all items in the list are 'None')
data Spec a e where
    (:*:) :: Spec a e1 -> Spec b e2 -> Spec (a :*: b) (Calc e1 e2)
    None :: Spec a Empty
    Some :: a -> Spec a NonEmpty


-- Only when two 'Empty' Specs are cons-ed will we get Empty 
type family Calc a b
type instance Calc Empty Empty = Empty 
type instance Calc Empty NonEmpty = NonEmpty 
type instance Calc NonEmpty Empty = NonEmpty 
type instance Calc NonEmpty NonEmpty = NonEmpty 


-- Example of usage
-- We need to specify the type here (GADT..) and not to forget to add 'NonEmpty'
foo :: Spec (Int :*: Bool :*: Char) NonEmpty -> Int 
foo (Some 5 :*: Some _ :*: Some _) = 1 
foo (Some _ :*: Some b :*: Some 'c') = if b then 2 else 22 
foo (Some 4 :*: None :*: None) = 3
foo (None :*: Some _ :*: None) = 4
foo (None :*: None :*: Some 'a') = 5
foo (Some _ :*: Some _ :*: Some _) = 42

-- Some test cases:
t1 = foo (Some 5 :*: Some True :*: Some 'a')    -- ==> 1
t2 = foo (Some 8 :*: Some False :*: Some 'c')   -- ==> 22
t3 = foo (Some 4 :*: None  :*: None)            -- ==> 3
t4 = foo (None :*: Some True :*: None)          -- ==> 4
t5 = foo (None :*: Some False :*: None)         -- ==> 4
t6 = foo (Some 1 :*: Some True :*: Some 'e')    -- ==> 42
-- t7 = foo (None :*: None :*: None)    -- Will not compile due to Empty/NonEmpty mismatch (at least one item should be not 'None')

PS Также: http://homepages.cwi.nl/~ralf/HList/ «Строго типизированные гетерогенные коллекции»

ОБНОВЛЕНИЕ : следуя комментариям автора: если мы опускаем требование статической проверки в случае «всего ничего» и впоследствии избавляемся от GADT (которые действительно требуют явной спецификации типа при каждом использовании)мы можем использовать стандартный ADT плюс несколько простых вычислений на уровне типов, чтобы создать случай «всего ничего» для динамической проверки:

{-# LANGUAGE TypeOperators, FlexibleInstances #-}

infixr 7 :*:
data a :*: b = a :*: b

-- type-level manipulations against our "custom-made tuple"
-- for now it only generates a tuple with all members set to Nothing, but can be extended
class Nothingable a where
    nothing :: a

instance Nothingable (Maybe a) where
    nothing = Nothing
instance (Nothingable b) => Nothingable (Maybe a :*: b) where
    nothing = Nothing :*: nothing

-- the same tests
foo (Just 5 :*: Just True :*: Just 'a') = 1
foo (Just _ :*: Just b :*: Just 'c') = if b then 2 else 22 
foo (Just 4 :*: Nothing :*: Nothing) = 3
foo (Nothing :*: Just _ :*: Nothing) = 4
foo (Nothing :*: Nothing :*: Just 'a') = 5
foo (Just _ :*: Just _ :*: Just _) = 42
-- test  for "all Nothing"
foo nothing = error "Need at least one non 'Nothing' case"

-- works for let and case bindings
boo t = 
    let (Just a :*: b) = t
    in case b of
         (Just _ :*: Nothing :*: Just c) -> c
         nothing                         -> 0

t1 = foo (Just 5 :*: Just True :*: Just 'a')   -- ==> 1
t2 = foo (Just 8 :*: Just False :*: Just 'c')  -- ==> 22
t3 = foo (Just 4 :*: Nothing :*: Nothing)      -- ==> 3
t4 = foo (Nothing :*: Just True :*: Nothing)   -- ==> 4
t5 = foo (Nothing :*: Just False :*: Nothing)  -- ==> 4
t6 = foo (Just 1 :*: Just True :*: Just 'e')   -- ==> 42
t7 = foo (Nothing :*: Nothing :*: Nothing)     -- ==> error

t8 = boo (Just undefined :*: Just True :*: Nothing :*: Just 5)   -- ==> 5
t9 = boo (Just undefined :*: Just True :*: Nothing :*: Nothing)  -- ==> 0 

2-Й ОБНОВЛЕНИЕ: Пожалуйста, не обращайте внимания на мое предыдущее «Обновление»: этонеправильно.Конечно, вы не можете сравнить с функцией nothing - здесь разрешен только конструктор данных или переменная, поэтому nothing считается переменной (как в вашем примере: someFun nothing = nothing isэквивалентно someFun a = a).Тем не менее, он может быть полезен как генератор кортежей «все ничего», и, если мы добавим функцию «test» isNothing в наш класс:

class Nothingable a where
    nothing :: a
    isNothing :: a -> Bool

instance Nothingable (Maybe a) where
    nothing = Nothing
    isNothing Nothing = True
    isNothing _ = False

instance (Nothingable b) => Nothingable (Maybe a :*: b) where
    nothing = Nothing :*: nothing
    isNothing (Nothing :*: a) = isNothing a
    isNothing _ = False

, то мы сможем использовать защиту Haskel98:

koo (Just 5 :*: Just "42" :*: Just True) = (Just True :*: Just 5.0 :*: Nothing)
koo ns | isNothing ns = nothing  -- 'nothing' here generates a tuple of three members all set to Nothing

или причудливые шаблоны вида (с расширением GHC "ViewPatterns"):

koo (Just 5 :*: Just "42" :*: Just True) = (Just True :*: Just 5.0 :*: Nothing)
koo (Just 5 :*: (isNothing -> True)) = (Just True :*: Nothing :*: nothing)

и:

boo t = 
    let (Just a :*: b) = t
    in case b of
         (Just _ :*: Nothing :*: Just c) -> c
         (isNothing -> True)             -> 0
         _                               -> error "failed"

Позор мне за предыдущие Update - это работало просто потому, что я поместил nothing match в качестве последнего случая в определениях функций (он совпадал с любым аргументом, не принятым в предыдущих случаях - привязывая его к этой вводящей в заблуждение переменной nothing),Извините за это!

2 голосов
/ 02 февраля 2011

Лично я бы использовал Either (a,b) (Either a b).Но это не совсем чистое средство для сопоставления с образцом.

Что вы действительно хотите использовать, так это функции проекции и шаблонные ограждения.

getLeft :: Or a b -> Maybe a
foo x
  | Just a <- getLeft x, Just b <- getRight x = ...
  | Just a <- getLeft x = ...

Редактировать: я только что реализовал другой подход - написать исключитель /катаморфизм по вашему типу.

import Data.Monoid

data Or a b = OrBoth a b | OrLeft a | OrRight b

orElim :: (t -> t2) -> (t1 -> t2) -> (t -> t1 -> t2) -> Or t t1 -> t2
orElim onLeft onRight onBoth x =
    case x of
      OrLeft  a  -> onLeft  a
      OrRight b  -> onRight b
      OrBoth a b -> onBoth  a b

morElim :: (Monoid a) => (t -> a) -> (t1 -> a) -> Or t t1 -> a
morElim onLeft onRight x =
    case x of
      OrLeft  a  -> onLeft  a
      OrRight b  -> onRight b
      OrBoth a b -> onLeft a `mappend` onRight b
1 голос
/ 03 февраля 2011

Это будет работать как решение, только если вы хотите, чтобы ваши Or a b структуры данных могли содержать конечное и управляемое количество типов данных.

В этом случае вы можетесделайте что-то вроде

data OrValue = OrInt Int | OrChar Char | OrString String | OrBool Bool

и тогда вы можете просто иметь дело с [OrValue] вместо того, чтобы развернуть группу конструкторов данных.

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

Для примера того, как этот метод используется в реальных приложениях, вы можете взглянуть на библиотеку Text.JSON (которую вы можете получитьот Cabal через cabal install json) - в частности, его тип данных JSValue.

1 голос
/ 02 февраля 2011

Почему ваши элементы OrBoth типа Or?Если вы знаете, что второе поле будет другим Or, то просто сгладьте структуру OrBoth x (Or a b), которая станет OrBothA x a | OrBothB x b.

Я также собирался предложить viewpatterns , но я вижуSCLV это скрыть!

0 голосов
/ 03 февраля 2011

Я бы предостерег от непосредственного использования типа Or, на мой взгляд, он слишком общий, чтобы с ним было приятно программировать.

В основном, или это тип данных Sum-and-Product - сумма равна либо в Haskell, а product - (,). С помощью sum и product - плюс null constructor () - вы можете моделировать алгебраические типы Haskell. Это модель для некоторых библиотек Generics, а также модель для некоторых форматов сериализации, таких как ASDL («Язык описания абстрактного синтаксиса»). Сумма и вид продукта действительно общие ...

Но из-за того, что он настолько общий, его трудно использовать - вы действительно хотите как можно быстрее выйти из представления суммы и продукта и использовать более прямой синтаксис, соответствующий данным, с которыми вы хотите работать. Другим недостатком Or является то, что он будет генерировать очень длинные подписи типов, когда у вас есть вложение. Также будет сложно написать функции для обхода вложенных структур Or на типизированном языке.

0 голосов
/ 03 февраля 2011

Немного поиграв, вот что я придумала.

data Or a b = Or { extractLeft :: Maybe a, extractRight :: Maybe b }
            deriving (Show, Eq)

Некоторые методы, кроме extractLeft и extractRight из синтаксиса записи ...

-- only works for "Or" with same type in left and right!
orExtract :: Or a a -> Maybe a
orExtract (Or (Just x) Nothing) = Just x
orExtract (Or Nothing (Just y)) = Just y
orExtract _ = Nothing

orLeft :: a -> Or a b
orLeft x = Or (Just x) Nothing

lEmpty (Or Nothing _) = True
lEmpty _ = False

orRight :: b -> Or a b
orRight x = Or Nothing (Just x)

rEmpty (Or _ Nothing) = True
rEmpty _ = False
rEmpty' x = case extractRight x of Nothing -> True
                           _ -> False


orBoth :: a -> b -> Or a b
orBoth x y = Or (Just x) (Just y)

Некоторые данные, построенные с этим типом (с использованием умных конструкторов)

foo :: Or Int Char
foo = orBoth 1 's'
bar :: Or Int a
bar = orLeft 3
baz :: Or a Char
baz = orRight 'f'

Метод, который использует охрану (не сопоставление с образцом ...)

orMethod orObj
    | lEmpty orObj = undefined
    | rEmpty orObj = undefined
    | lEmpty lObj  = undefined
    | rEmpty rObj = undefined
        where (Just lObj) = extractLeft orObj
              (Just rObj) = extractRight orObj

Я не уверен, что использование такого рода вещей - хорошая идея. Я согласен с подходом drvitek; вы будете использовать конечное число типов, и поэтому ваши структуры данных могут быть обобщены в один и тот же «тип», после чего вы сможете использовать операции и шаблоны списков.

...