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

Почему, возможно, включает Just?

Благодаря отличным ответам здесь, я понимаю (явно ограниченным образом) цель Haskell Maybe и что ее определение

data Maybe a = Nothing | Just a

однако я не являюсь понятным, почему именно Just является частью этого определения. Насколько я могу судить, именно здесь определяется Just, но соответствующая документация ничего не говорит об этом.

Правильно ли я думаю, что основное преимущество использования Just в определении Maybe, а не просто

data Maybe a = Nothing | a

заключается в том, что он позволяет сопоставлять шаблоны с Just _ и для полезных функций, таких как isJust и fromJust?

Почему Maybe определяется первым, а не последним?

4b9b3361

Ответ 1

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

Ваше определение не соответствует тому, как работают алгебраические типы данных.

data Maybe a = Nothing | a

Здесь нет для a. Как мы скажем Maybe a, кроме обычного, развернутого a в вашем случае?

Maybe имеет конструктор Just, поскольку он должен иметь конструктор по дизайну.

Другие языки имеют типы профсоюзов, которые могут работать так, как вы себе представляете, но они не подходят для Haskell. Они играют по-разному на практике и склонны быть склонными к ошибкам.

Есть несколько серьезных причин для предположения о том, что тегированные союзы предпочитают обычные типы профсоюзов. Они хорошо играют с типом вывода. Союзы в реальном коде часто имеют метку в любом случае¹. И, с точки зрения элегантности, тегированные союзы являются языком естественным, потому что они являются двойственными продуктами (т.е. кортежами и записями). Если вам интересно, я написал об этом в сообщении в блоге представляющем и мотивирующим алгебраические типы данных.

сноски

¹ Я ​​играл с типами union в двух местах: TypeScript и C. TypeScript компилируется в JavaScript, который динамически типизирован, что означает, что он отслеживает тип значения во время выполнения - в основном тег.

C не на практике, но на практике примерно 90% использования типов профсоюзов либо имеют тег, либо эффективно эмулируют подтипирование структуры. Один из моих профессоров действительно сделал эмпирическое исследование о том, как профсоюзы используются в реальном коде С, но я не помню, какая бумага была в стороне.

Ответ 2

Другой способ взглянуть на него (помимо ответа Тихона) - рассмотреть другой один из основных типов Haskell Either, который определяется следующим образом:

-- | A value that contains either an @[email protected] (the 'Left') constructor) or 
-- a @[email protected] (the 'Right' constructor).
data Either a b = Left a | Right b

Это позволяет вам иметь такие значения:

example1, example2 :: Either String Int
example1 = Left "Hello!"
example2 = Right 42

... но также как этот:

example3, example4 :: Either String String
example3 = Left "Hello!"
example4 = Right "Hello!"

Тип Either String String, первый раз, когда вы его встретите, звучит как "либо String, либо String", и поэтому вы можете думать, что он такой же, как только String. Но это не так, потому что союзы Haskell являются мечеными союзами, поэтому Either String String записывает не только String, но также и те из "тегов" (конструкторы данных, в данном случае Left и Right)) был использован для его построения. Поэтому, хотя обе альтернативы несут String в качестве своей полезной нагрузки, вы можете сказать, как было построено какое-то одно значение. Это хорошо, потому что есть много случаев, когда альтернативы одного типа, но конструкторы/теги придают дополнительный смысл:

data ResultMessage = FailureMessage String | SuccessMessage String

Здесь конструкторы данных FailureMessage и SuccessMessage, и вы можете догадаться из названий, хотя полезная нагрузка в обоих случаях равна String, они означают совсем другие вещи!

Итак, вернемся к Maybe/Just, что происходит здесь, так как Haskell просто работает одинаково: каждая альтернатива типа union имеет отдельный конструктор данных, который всегда должен использоваться для построения и сопоставления значений шаблона его типа. Даже если сначала вы можете подумать, что можно было бы угадать это из контекста, это просто не делает этого.

Есть и другие причины, немного более технические. Во-первых, правила для ленивой оценки определяются с точки зрения конструкторов данных. Короткий вариант: ленивая оценка означает, что если Haskell вынужден заглянуть внутрь значения типа Maybe a, он попытается выполнить минимальный объем работы, необходимый для выяснения, похоже ли это на Nothing или как Just x Возможно, он не заглянет внутрь x, когда он это сделает.

Во-вторых: язык должен различать типы типа Maybe a, Maybe (Maybe a) и Maybe (Maybe (Maybe a)). Если вы думаете об этом, если у нас есть определение типа, которое работает так, как вы писали:

data Maybe a = Nothing | a  -- NOT VALID HASKELL

... и мы хотели сделать значение типа Maybe (Maybe a), вы не смогли бы разделить эти два значения:

example5, example6 :: Maybe (Maybe a)
example5 = Nothing
example6 = Just Nothing

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

-- Map of persons to their favorite number.  If we know that some person
-- doesn't have a favorite number, we store `Nothing` as the value for
-- that person.
favoriteNumber :: Map Person (Maybe Int)

... и хотите найти запись:

Map.lookup :: Ord k => Map k v -> k -> Maybe v

Итак, если мы посмотрим на mary на карте, мы имеем:

Map.lookup favoriteNumber mary :: Maybe (Maybe Int)

И теперь результат Nothing означает Марию не на карте, а Just Nothing означает Марию на карте, но у нее нет любимого номера.

Ответ 3

Just является конструктором, a один будет иметь тип a, когда Just a создает другой тип Maybe a.

Ответ 4

Maybe a сконструирован таким образом, чтобы иметь еще одно значение, чем тип a. В теории типов иногда она записывается как 1 + a (вплоть до iso), что делает этот факт еще более очевидным.

В качестве эксперимента рассмотрим тип Maybe (Maybe Bool). Здесь мы имеем значения 1 + 1 + 2, а именно:

Nothing
Just Nothing
Just (Just False)
Just (Just True)

Если нам было разрешено определять

data Maybe a = Nothing | a

мы потеряли бы различие между случаями Just Nothing и Nothing, так как больше не нужно Just, чтобы разделить их. Действительно, Maybe (Maybe a) рухнет на Maybe a. Это было бы неудобным частным случаем.