Выбор среди альтернатив в алгебраическом типе данных Haskell
-
13-09-2019 - |
Вопрос
Когда набираете X
определяется как:
data X =
X { sVal :: String } |
I { iVal :: Int } |
B { bVal :: Bool }
и я хочу, чтобы Int
внутри X
значение, если оно есть, в противном случае равно нулю.
returnInt :: X -> Int
Как я могу определить, какой тип X
аргумент к returnInt
есть?
Решение
Просто чтобы прояснить здесь один момент, позвольте мне переписать ваш тип данных, чтобы избежать двусмысленностей в значении 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 ).
Другие советы
Используйте сопоставление с образцом.
returnInt :: X -> Int
returnInt (I x) = x
returnInt _ = 0
Используйте более гибкое определение для всех возможных 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]
Учитывая функцию, подобную этой:
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"