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



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



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


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

535   6  

6 ответов:

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

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

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

ключевое слово 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. Мне нравится окрашивать конструкторы типов (дерево) синим и конструкторы значений (лист, узел) красным. Не должно быть синий в выражениях и (если вы не используете расширенные функции) нет красного в типах. Встроенный тип 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)

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

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

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 но не возвращает ничего интересного.

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

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

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

монадические вещи, как 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..."

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

когда вы создаете декартово произведение / кортеж этих типов, вы будете наблюдать то же самое поведение: (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 элементы, если не считать _|_ в качестве значения.

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

() - Это имя набора, который содержит один элемент с именем ().

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

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

Comments

    Ничего не найдено.