"Привет" это преобразователь или в нормальной форме?

И 1, и "hello" находятся в нормальной форме, поэтому не являются преобразователями. Так почему же GHCI показывает, что "hello" — это преобразователь?

ghci> x = 1::Int
ghci> :sprint x
x = 1

ghci> x = "hello"::String
ghci> :sprint x
x = _

Редактировать: я копирую комментарий из моего предыдущего поста, который пытается объяснить этот вопрос.

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

Может ли кто-нибудь дать мне несколько статей или документов об этом?

почему вы используете :: String, вы работаете с расширением OverloadedStrings?

Willem Van Onsem 16.05.2023 15:12

что касается "hello", это то, что будет оцениваться как список, поскольку "hello" в конечном итоге оценивается как ('h':('e':('l':('l':('o':[]))))).

Willem Van Onsem 16.05.2023 15:13

@WillemVanOnsem Результат тот же, независимо от того, включаете ли вы OverloadedStrings или нет. Я пишу :: String, чтобы было понятно, что это не связано с ограничением мономорфизма.

Trung Do 16.05.2023 15:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
3
119
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы скажете GHC напечатать обесахаривание "hello" в GHC Core, вы увидите, что строковый литерал обессахаривается до вызова unpackCString#. Фактическое содержимое строки хранится компактно "hello"#, а функция unpackCString# строит соответствующий связанный список 'h' : 'e' : 'l' : 'l' : 'o' : [] по запросу, поэтому при потоковом использовании требуется очень мало памяти.

> :set -ddump-ds -dsuppress-all
> x = "hello"

==================== Desugared ====================
letrec {
  x_aCa = letrec { x_aCe = unpackCString# "hello"#; } in x_aCe; } in
returnIO (: (unsafeCoerce# x_aCa) [])
Ответ принят как подходящий

Как я уже говорил ранее, это в основном связано с тем, как GHCi решает хранить значения, а не с тем, что указано в стандарте Haskell. Преобразователи/лень — это деталь реализации того, как достигается нестрогость, и иногда они также являются хорошей ментальной моделью, но не всегда. Строгость касается функций и того, как ⊥-значения могут или не могут распространяться. Любая мудрость о том, где преобразователь может или не может появиться, в основном является побочным продуктом этого.

Но есть еще пара вещей, о которых следует помнить на чистом уровне value/thunk.

Полиморфные значения всегда оцениваются по требованию; в основном это функции с невидимыми аргументами. В GHCi (в котором ограничение мономорфизма отключено по умолчанию) легко определить полиморфные значения без необходимости, в частности числовые литералы. Тогда :sprint всегда будет давать только _.
Это не проблема в ваших примерах: явное ограничение конкретным типом обеспечивает мономорфные привязки.

Пока вы напрямую используете простые конструкторы, есть большая вероятность, что компилятор/интерпретатор сохранит их напрямую в нормальной форме. Он делает это, например, для чего-то вроде ('a', (Just 'b','c')). Но я не думаю, что это когда-либо гарантировано, и обычно это в любом случае не имеет значения, потому что буквальные конструкторы только доходят до вас. Все, что гарантируется, — это то, что значение, определенное как внешний конструктор, может быть оценено как WHNF, не сталкиваясь с ⊥, но здесь мы снова находимся в области свойств строгости.

Как правило, значения, которые вы связываете, поступают из какой-то функции и, таким образом, начинаются как переходники. В частности, это относится и ко многим выражениям, которые выглядят как простые конструкторы, но таковыми не являются: большинство литералов в Haskell могут быть интеллектуальными литералами либо по умолчанию, либо через расширения. Наиболее известно, что числовые литералы неявно заключены в fromInteger, чтобы быть полиморфными для любого типа Num. Таким образом, это действительно немного удивительно, что

x = 1::Int

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

Стоит отметить, что это происходит не для всех числовых литералов:

ghci> let y = 1678796789876876987698768798769876986780987609876 :: Integer
ghci> :sprint y
y = _

В этом случае число больше не вписывается в 64-битное число, поэтому менее ясно, что размещение его прямо в памяти выгодно; оказывается, GHC не рискует в этом случае.

Теперь для строк у нас была бы почти такая же ситуация, если бы -XOverloadedStrings был активен, и в этом случае все строковые литералы должны были бы быть заключены в fromString . Но нет никакой гарантии, что наоборот: то, что расширение не активно, не означает, что GHC не может обернуть литерал какой-либо другой функцией. Как показал Ли-яо Ся, он оборачивает его в низкоуровневую функцию unpackCString#. (Это, безусловно, имеет смысл с точки зрения памяти, потому что строка C непрерывна и эффективна в памяти, тогда как строки связанного списка Haskell откровенно ужасны для хранения, занимая примерно в 16 раз больше памяти.) Это законно, пока компилятор убедитесь, что вы не столкнетесь с ⊥ при оценке строки.

В этом случае явное определение строки с помощью простых конструкторов приводит к ее немедленному сохранению в нормальной форме:

ghci> let x = "hello" :: String
ghci> :sprint x
x = _
ghci> let x' = 'h':'e':'l':'l':'o':[]
ghci> :sprint x'
x' = "hello"

Спасибо за подробный ответ. Итак, то, что говорится в викибуке, является просто ментальной моделью, а не тем, как GHC на самом деле реализует ленивую оценку. Но порядок или оценка по-прежнему верны, верно? Эти значения оцениваются по слоям: оценка исходит из самых внешних конструкторов данных выражений и работает внутрь.

Trung Do 16.05.2023 17:55

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

leftaroundabout 16.05.2023 18:03

@TrungDo Нет. Факт: если вы продолжаете сокращать выражение, вы либо приходите к нормальной форме, либо застреваете в бесконечном цикле. Далее, любая последовательность редукций, дающая нормальную форму, дает ту же нормальную форму. Это означает, что GHC может выбрать для выражения любую стратегию вычисления, которая ему нравится, при условии, что он позаботится о том, чтобы избежать ненужных бесконечных циклов. Ленивое вычисление — это простая стратегия, которая гарантированно найдет нормальную форму, если она есть. Но если GHC знает лучше (скорее в оптимизированном скомпилированном коде, чем в GHCi), он может выполнять неленивые вычисления.

HTNW 16.05.2023 18:09

@HTNW Спасибо. Так что мне не следует заморачиваться с переходниками и WHNF, а просто нужно помнить о свойстве нестрогости: f(⊥) ≠ ⊥

Trung Do 16.05.2023 18:16

@TrungDo Нет, WHNF по-прежнему важен. Все в моем предыдущем комментарии более или менее справедливо как для WHNF, так и для нормальной формы, а WHNF — это концепция, на которой основана идея строгости и остальная часть спецификации Haskell. Но думать о преобразователях действительно излишне, пока вам не нужно беспокоиться о производительности (или использовать unsafePerformIO).

HTNW 16.05.2023 18:31

@HTNW Спасибо. Теперь это имеет смысл для меня. К сожалению, каждый текст, который я читал, устанавливает сильную связь между преобразователями и WHNF.

Trung Do 16.05.2023 18:39

Другие вопросы по теме