Подтвердить что ты не робот

Как получить значение Maybe в Хаскеле

Я относительно новичок в Haskell и начал читать "Real World Haskell".

Я просто наткнулся на тип Maybe, и у меня возник вопрос о том, как получить фактическое значение из Just 1 например.

Я написал следующий код:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = 0

Это прекрасно работает, если я использую:

combine (Just 1) Nothing (Just 2)

Но если я изменю, например, 1 на строку, это не сработает.

Я думаю, я знаю почему: потому что eliminate должно возвращать один тип, который, в данном случае, Int. Но как я могу изменить eliminate чтобы иметь дело хотя бы со строками (или, может быть, со всеми типами)?

4b9b3361

Ответ 1

[edit from Author, 6 лет спустя] Это бесполезно длинный ответ, и я не уверен, почему он был принят. Используйте maybe или Data.Maybe.fromMaybe, как указано в самом высоком ответе. Далее следует скорее мысленный эксперимент, чем практический совет.

Итак, вы пытаетесь создать функцию, которая работает для группы разных типов. Это подходящее время для занятий. Если вы запрограммировали на Java или С++, класс в Haskell похож на интерфейс на этих языках.

class Nothingish a where
    nada :: a

Этот класс определяет значение nada, которое предполагается эквивалентным классу Nothing. Теперь самое интересное: создание экземпляров этого класса!

instance Nothingish (Maybe a) where
    nada = Nothing

Для значения типа Maybe a Ничто-подобное значение, ну, Nothing! Это будет странный пример через минуту. Но до этого пусть также создаст список экземпляров этого класса.

instance Nothingish [a] where
    nada = []

Пустой список вроде как ничего, не так ли? Итак, для String (который является списком Char), он вернет пустую строку "".

Числа также являются легкой реализацией. Вы уже указали, что 0 явно представляет "Nothingness" для чисел.

instance (Num a) => Nothingish a where
    nada = 0

На самом деле это не будет работать, если вы не добавите специальную строку в начало файла

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

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

Итак, теперь у вас есть этот класс и эти экземпляры... теперь позвольте просто перезаписать свою функцию, чтобы использовать их!

eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing  = nada

Заметьте, что я только изменил 0 на nada, а остальное - то же самое. Пусть дается спин!

ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**

Выглядит отлично и ценно. Обратите внимание, что (Просто Ничто) не превращается в Ничего, понимаете? Это был странный пример, возможно, в "Может быть". В любом случае... как насчет eliminate Nothing? Ну, результирующий тип неоднозначен. Он не знает, чего мы ожидаем. Поэтому мы должны сказать, какой тип мы хотим.

ghci> eliminate Nothing :: Int
0

Идите дальше и попробуйте его для других типов; вы увидите, что он получает nada для каждого из них. Итак, теперь, когда вы используете эту функцию со своей функцией combine, вы получаете следующее:

ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah

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

ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)

Или вы можете ограничить типы, которые позволяет ваша функция, явно помещая свою подпись типа в источник. Это имеет смысл, если логическое использование функции будет состоять в том, что оно используется только с параметрами одного и того же типа.

combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)

Теперь он работает только в том случае, если все три вещи, которые могут быть одного типа. Таким образом, он выведет, что Nothing является тем же самым типом, что и остальные.

ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)

Нет двусмысленности, да! Но теперь это смешение и совпадение, как мы это делали раньше.

ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah  Couldn't match expected type  blah blah
blah blah blah    against inferred type         blah blah

Ну, я думаю, это был достаточно длинный и раздутый ответ. Наслаждайтесь.

Ответ 2

Из стандартной Prelude,

maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x

Если задано значение по умолчанию и функция, примените функцию к значению в Maybe или верните значение по умолчанию.

Ваше eliminate может быть записано, maybe 0 id, например, применить функцию идентификации или вернуть 0.

Из стандартного Data.Maybe,

fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x

Это частичная функция (не возвращает значение для каждого входа, в отличие от функции итога), но извлекает значение, когда это возможно.

Ответ 3

Я тоже новичок в Haskell, поэтому я не знаю, существует ли это на платформе (я уверен, что это так), но как насчет функции "get или else", чтобы получить значение, если оно существует, else вернет значение по умолчанию?

getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d  = d

Ответ 4

Это ответ, который я искал, когда пришел к этому вопросу:

https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust

... и аналогично, для Either:

https://hackage.haskell.org/package/either-unwrap-1.1/docs/Data-Either-Unwrap.html

Они предоставляют функции, которые я написал бы себе, которые разворачивают значение из своего контекста.

Ответ 5

Подпись типа функции eliminate:

eliminate :: Maybe Int -> Int

Это потому, что он возвращает 0 для Nothing, заставляя компилятор предполагать, что a :: Int в вашей функции eliminate. Следовательно, компилятор выводит сигнатуру типа функции combine:

combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)

и именно поэтому он не работает, когда вы передаете ему строку.

Если вы написали это как:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = undefined

тогда он работал бы со String или любым другим типом. Причина заключается в том факте, что undefined :: a делает полиморфные eliminate и применимы к типам, отличным от Int.

Конечно, это не цель вашего кода, т.е. Сделать функцию объединения общей.

Действительно, даже если приложение combine с некоторыми аргументами Nothing будет успешным (это потому, что Haskell по умолчанию ленив), как только вы попытаетесь оценить результаты, вы получите ошибку времени выполнения, так как undefined не может быть оценен как нечто полезное ( Проще говоря,