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

Что такое() в Haskell, точно?

Я читаю "Learn You a Haskell", и в главах монады мне кажется, что () рассматривается как своего рода "нуль" для каждого типа. Когда я проверяю тип () в GHCi, я получаю

>> :t ()
() :: ()

что является чрезвычайно запутанным утверждением. Кажется, что () - это тип для себя. Я смущен тем, как он вписывается в язык, и как он, похоже, способен стоять на любом типе.

4b9b3361

Ответ 1

tl; dr () не добавляет значение "null" для каждого типа, черт нет; () является "тупым" значением в собственном типе: ().

Позвольте мне немного отойти от вопроса и обратиться к общему источнику смятения. Главное, что нужно усвоить при изучении Haskell, - это различие между языком выражения и его типом языка. Вы, вероятно, знаете, что эти два человека раздельны. Но это позволяет использовать один и тот же символ в обоих, и это то, что происходит здесь. Есть простые текстовые подсказки, чтобы рассказать вам, на каком языке вы смотрите. Вам не нужно анализировать весь язык, чтобы обнаружить эти сигналы.

Верхний уровень модуля Haskell живет по умолчанию в языке выражений. Вы определяете функции, записывая уравнения между выражениями. Но когда вы видите foo:: bar в языке выражений, это означает, что foo - выражение, а bar - его тип. Поэтому, когда вы читаете () :: (), вы видите инструкцию, которая связывает () в языке выражения с () в языке типов. Два символа () означают разные вещи, потому что они не на одном языке. Это повторение часто вызывает путаницу для новичков, пока разделение языка выражения/типа не устанавливается в их подсознании, и в этот момент оно становится мнемоническим.

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

data TyCon tyvar ... tyvar = ValCon1 type ... type |  ...  | ValConn type ... type

В таком объявлении к языку типов добавляется конструктор типов TyCon, а конструкторы значения ValCon добавляются к языку выражения (и его подъязыку паттерна). В объявлении data вещи, стоящие в местах аргументов для ValCon s, сообщают вам типы, заданные для аргументов, когда этот ValCon используется в выражениях. Например,

data Tree a = Leaf | Node (Tree a) a (Tree a)

объявляет конструктор типа Tree для двоичных типов деревьев, хранящих элементы в узлах, значения которых задаются конструкторами значений Leaf и Node. Мне нравится использовать конструкторы цветного дерева (Tree) blue и конструкторы значений (Leaf, Node) red. В выражениях не должно быть синих, и (если вы не используете расширенные функции) нет красных типов. Можно объявить встроенный тип Bool,

data Bool = True | False

добавление синего Bool к типу языка, а красный True и False - к языку выражения. К сожалению, моя markdown-fu неадекватна задаче добавления цветов к этому сообщению, поэтому вам просто нужно научиться добавлять цвета в голову.

Тип "unit" использует () в качестве специального символа, но он работает так, как будто объявлено

data () = ()  -- the left () is blue; the right () is red

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

data (a, b) = (a, b)

добавление (,) к языкам типов и выражений. Но я отвлекаюсь.

Таким образом, тип (), часто произносится как "Единица", представляет собой тип, содержащий одно значение, о котором стоит сказать: это значение написано (), но в языке выражения и иногда произносится как "void". Тип с одним значением не очень интересен. Значение типа () вносит нулевые биты информации: вы уже знаете, что это должно быть. Итак, пока нет особого типа () для указания побочных эффектов, он часто отображается как компонент значения в монадическом типе. У монадических операций, как правило, есть типы, которые выглядят как

val-in-type-1 -> ... -> val-in-type-n -> effect-monad val-out-type

где тип возвращаемого типа - это приложение типа: функция сообщает вам, какие эффекты возможны, и аргумент указывает вам, какое значение генерируется операцией. Например

put :: s -> State s ()

который читается (потому что приложение ассоциируется с левым [ "как мы все делали в шестидесятые годы", Роджер Хиндли]) как

put :: s -> (State s) ()

имеет один тип ввода значения s, эффект-монада State s и тип вывода значения (). Когда вы видите () как тип вывода значения, это означает, что "эта операция используется только для ее эффекта, при этом значение неинтересно". Аналогично

putStr :: String -> IO ()

передает строку в stdout, но не возвращает ничего интересного.

Тип () также полезен в качестве типа элемента для контейнероподобных структур, где он указывает, что данные состоят только из формы без какой-либо интересной полезной нагрузки. Например, если Tree объявлено выше, то Tree () является типом двоичных древовидных форм, не сохраняя ничего интересного в узлах. Точно так же [()] - это тип списков тусклых элементов, и если в элементах списка нет ничего интересного, то единственной информацией, которую он вносит, является его длина.

Подводя итог, () является типом. Его одно значение, (), имеет одно и то же имя, но это нормально, потому что языки типов и выражений являются отдельными. Полезно иметь тип, представляющий "нет информации", потому что в контексте (например, монады или контейнера) он сообщает вам, что интересен только контекст.

Ответ 2

Тип () можно рассматривать как кортеж нулевого элемента. Это тип, который может иметь только одно значение, и, следовательно, он используется там, где вам нужно иметь тип, но вам фактически не нужно передавать какую-либо информацию. Здесь несколько применений для этого.

Монадические вещи, такие как IO и State, имеют возвращаемое значение, а также выполняют побочные эффекты. Иногда единственной точкой операции является выполнение побочного эффекта, например, запись на экран или сохранение некоторого состояния. Для записи на экран putStrLn должен иметь тип String -> IO ? - IO всегда должен иметь некоторый тип возвращаемого значения, но здесь нет ничего полезного для возврата. Какой тип мы должны вернуть? Мы могли бы сказать Int и всегда возвращать 0, но это вводит в заблуждение. Поэтому мы возвращаем (), тип, который имеет только одно значение (и, следовательно, полезную информацию), чтобы указать, что ничего полезного не возвращается.

Иногда бывает полезно иметь тип, который не может иметь полезных значений. Подумайте, если бы вы внедрили тип Map k v, который сопоставляет ключи типа k значениям типа v. Затем вы хотите реализовать Set, который действительно похож на карту, за исключением того, что вам не нужна часть значения, просто клавиши. На языке, таком как Java, вы можете использовать логическое значение как тип значения фиктивного типа, но на самом деле вам просто нужен тип, который не имеет полезных значений. Таким образом, вы могли бы сказать type Set k = Map k ()

Следует отметить, что () не особенно магия. Если вы хотите, вы можете сохранить его в переменной и выполнить сопоставление с шаблоном (хотя не так много):

main = do
  x <- putStrLn "Hello"
  case x of
    () -> putStrLn "The only value..."

Ответ 3

Он называется типом Unit, обычно используемым для представления побочных эффектов. Вы можете думать об этом смутно как Void в Java. Подробнее здесь и здесь и т.д. Что может сбить с толку что () синтаксически представляет как тип, так и его единственный литерал значения. Также обратите внимание, что он не похож на null в Java, что означает, что ссылка undefined - () является просто кортежем размера 0.

Ответ 4

Мне очень нравится думать о () по аналогии с кортежами.

(Int, Char) - тип всех пар a Int и a Char, поэтому значения - это все возможные значения Int, пересекающиеся со всеми возможными значениями Char. (Int, Char, String) аналогично типу всех троек a Int, a Char и a String.

Легко видеть, как продолжать распространять этот рисунок вверх, но как насчет вниз?

(Int) был бы типом "1-кортеж", состоящим из всех возможных значений Int. Но это было бы проанализировано Haskell, так как просто помещаем круглые скобки вокруг Int и, таким образом, являемся просто типом Int. И значения в этом типе будут (1), (2), (3) и т.д., Которые также будут просто проанализированы как обычные значения Int в круглых скобках. Но если вы думаете об этом, то "1-кортеж" точно такой же, как только одно значение, поэтому нет необходимости фактически их существовать.

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

Вот как это работает. Магии нет, и этот тип () и его значение () никак не обрабатываются специально языком.

() на самом деле не рассматривается как "нулевое значение для любого типа" в примерах монадов в книге LYAH. Всякий раз, когда используется тип (), единственным значением, которое может быть возвращено, является (). Поэтому он используется как тип, чтобы явно сказать, что не может любое другое возвращаемое значение. Точно так же, как предполагается, что возвращается другой тип, вы не можете вернуть ().

Следует иметь в виду, что когда куча монадических вычислений складывается вместе с блоками do или операторами типа >>=, >> и т.д., они будут строить значение типа m a для некоторой монады m. Этот выбор m должен оставаться неизменным во всех компонентах (нет способа составить Maybe Int с IO Int таким образом), но a может и очень часто различается на каждом этапе.

Итак, когда кто-то вставляет IO () в середину вычисления IO String, не используя () как нуль в типе String, он просто использует IO () на пути к созданию a IO String, так же, как вы могли бы использовать Int на пути к созданию String.

Ответ 5

Путаница исходит от других языков программирования: "void" означает в большинстве императивных языков, что в памяти отсутствует структура, сохраняющая значение. Это кажется непоследовательным, потому что "boolean" имеет 2 значения вместо 2 бит, тогда как "void" не имеет битов вместо значений no, но там речь идет о том, что функция возвращает в практическом смысле. Точнее: его одно значение не потребляет бит памяти.

Пусть на мгновение игнорирует дно значения (записано _|_)...

() называется Unit, написанным как нулевой набор. Он имеет только одно значение. И он не называется Void, потому что Void не имеет даже никакого значения, поэтому никакая функция не может быть возвращена.


Обратите внимание: Bool имеет 2 значения (True и False), () имеет одно значение (()) и Void не имеет значения (его не существует). Они похожи на наборы с двумя/одним/без элементов. Наименьшая память, необходимая для хранения их значения, равна 1 бит/без бит/невозможна, соответственно. Это означает, что функция, возвращающая значение (), может возвратиться с результатом результата (очевидным), которое может оказаться бесполезным для вас. Void, с другой стороны, подразумевает, что эта функция никогда не вернется и никогда не даст вам никакого результата, потому что результата не будет.

Если вы хотите присвоить "это значение" имя, возвращаемое функцией, которое никогда не возвращается (да, это звучит как crazytalk), тогда назовите его bottom ( "_|_", написанное как обратное T). Он может представлять собой цикл исключения или бесконечности или тупик или "просто ждать дольше". (Некоторые функции возвращаются только снизу, если один из их параметров внизу.)

Когда вы создаете декартово произведение/кортеж этих типов, вы будете наблюдать такое же поведение:    (Bool,Bool,Bool,(),()) имеет 2 · 2 · 2 · 1 · 1 = 6 разных значений. (Bool,Bool,Bool,(),Void) подобен множеству {t, f} × {t, f} × {t, f} × {u} × {}, который имеет 2 · 2 · 2 · 1 · 0 = 0 элементов, если вы не считаете _|_ в качестве значения.

Ответ 6

Еще один угол:

() - это имя набора, содержащего один элемент с именем ().

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

Помните: в Haskell тип - это набор, который имеет свои возможные значения как элементы в нем.