Выбор среди альтернатив в алгебраическом типе данных Хаскелла - PullRequest
3 голосов
/ 07 декабря 2009

Когда тип X определяется как:

data X = 
    X { sVal :: String } |
    I { iVal :: Int } |
    B { bVal :: Bool }

и я хочу Int внутри значения X, если оно есть, иначе ноль.

returnInt :: X -> Int

Как определить тип X аргумента returnInt?

Ответы [ 4 ]

13 голосов
/ 07 декабря 2009

Использовать сопоставление с образцом.

returnInt :: X -> Int
returnInt (I x) = x
returnInt _     = 0
10 голосов
/ 07 декабря 2009

Используйте более гибкое определение для всех возможных X значений:

returnInt :: X -> Maybe Int
returnInt (I i) = Just i
returnInt _ = Nothing

Затем вы можете использовать maybe для определенного значения по умолчанию, которое вы хотите - 0 может быть допустимым значением (это известно как проблема с полупредикатом ):

*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> maybe (-1) id (returnInt $ X "yo")
-1

Напротив, частичные функции рискуют исключения времени выполнения:

*Main> let returnInt (I i) = i
*Main> :t returnInt
returnInt :: X -> Int
*Main> returnInt (B True)
*** Exception: <interactive>:1:4-22: Non-exhaustive patterns in function returnInt

Если вы чувствуете себя действительно лягушкой, вы можете использовать MonadPlus

returnInt :: (MonadPlus m) => X -> m Int
returnInt (I i) = return i
returnInt _ = mzero

для получения еще большей гибкости:

*Main> maybe 0 id (returnInt $ X "")
0
*Main> maybe 0 id (returnInt $ I 123)
123
*Main> returnInt (I 123) `mplus` returnInt (I 456) :: [Int]
[123,456]
4 голосов
/ 07 декабря 2009

С учетом такой функции:

returnInt :: X -> Int
returnInt x = {- some integer -}

... тип x всегда X. Вас интересует, использует ли x конструктор типа X, I или B.

Используйте сопоставление с образцом, чтобы определить разницу:

returnInt :: X -> Int
returnInt (X _) = error "needed an Int, got a String"
returnInt (I { iVal = n }) = n
returnInt (B _) = error "needed an Int, got a Bool"
3 голосов
/ 09 декабря 2009

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

data SomeType = X { myString :: String} | I {myInt :: Int} | B {myBool :: Bool}

В этом определении нет типов X, I и B. X, I и B являются конструкторами , которые создают значение типа Sometype. Обратите внимание, что происходит, когда вы спрашиваете ghci, каков тип любого значения, созданного этими конструкторами типов:

*Main> :t (I 5)
(I 5) :: Sometype 

*Main> :t (B False)
(B False) :: Sometype

Они принадлежат к одному и тому же типу !!

Так же, как вы можете использовать X, I и B для конструирования типов, вы можете использовать сопоставление с образцом для деконструкции типа, как это было сделано в других ответах выше:

returnInt :: SomeType -> Int 
returnInt (I x) = x        -- if the pattern matches (I x) then return x
returnInt _  = error "I need an integer value, you moron"  -- throw an error otherwise

Просто помните, что сопоставление с образцом происходит по порядку: если значение совпадает с шаблоном в некоторой строке, шаблоны в строках ниже не будут выполняться.

Обратите внимание, что когда вы определяете свой тип, как вы, используя то, что называется Синтаксис записи (просто посмотрите здесь: http://en.wikibooks.org/wiki/Haskell/More_on_datatypes), вы получаете такие функции бесплатно !!

Попробуйте посмотреть тип myInt, например:

*Main> :t myInt
myInt :: SomeType -> Int

И посмотрите, что делает эта функция:

*Main> myInt (I 5)
5

*Main> myInt (B False)
*** Exception: No match in record selector Main.myInt

Это именно то поведение returnInt, которое определено выше. Странное сообщение об ошибке просто говорит вам, что функция не знает, как обращаться с членом типа SomeType, который не соответствует (I x).

Если вы определяете свой тип, используя более распространенный синтаксис:

data SomeType2 = X String | I Int | B Bool

тогда вы теряете эти прекрасные функции записи.

Сообщения об ошибках прекращают выполнение программы. Это иногда раздражает. Если вам нужно более безопасное поведение для ваших функций, ответ GBacon - это просто способ сделать это. Узнайте о типе Maybe a и используйте его, чтобы справиться с такого рода вычислениями, которые должны возвращать некоторое значение или ничего не возвращать (попробуйте: http://en.wikibooks.org/wiki/Haskell/Hierarchical_libraries/Maybe).

...