Что такое () в Haskell, точно?
Я читаю узнать вы на Haskell, и в главах монады, мне кажется, что () рассматривается как своего рода" null " для каждого типа. Когда я проверяю тип () в GHCi, я получаю
>> :t ()
() :: ()
что является чрезвычайно запутанной заявление. Кажется, что () тип все для себя. Я смущен тем, как он вписывается в язык, и как он, кажется, может стоять для любого типа.
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, aCharи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тип, это просто используя anIO ()на пути к построениюIO String, так же, как вы могли бы использовать anIntна пути к построению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