2013-04-19 6 views
4

Мне очень нравится Haskell, однако одна из главных вещей, которая касается меня о Haskell, затрудняет рассуждение о использовании пространства. В принципе, вероятность взлома и рекурсии, похоже, создает некоторые сложные ситуации, когда кажется, что нужно быть очень осторожным, добавляя правильную строгость, чтобы программа не исчерпала память на определенных входах.Тип принудительного «строгого/императивного» подмножества/версии Haskell

Что мне нравится в C/C++, так это то, что я могу быстро быть уверенным в верхних границах использования пространства программ, в частности, если вы избежите рекурсии. Переменные очевидны.

Что мне было интересно, если есть способ создать типичный «императивный» подмножество Haskell, который не имеет разладов и строг.

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

Я думал, что-то вроде «Строгий Монады», но, возможно, не является правильной формы, чтобы сделать это.

+2

Вы видели [язык учеников] (http://disciple.ouroborus.net/)? –

+0

Спасибо. Я искал способ сжать это в текущий Haskell (ну, GHC, который есть), но это интересно. – Clinton

+3

Распространенность утечек памяти в программах с ручным управлением памятью вызывает опровержение вашего тезиса о том, что легко установить верхние границы использования пространства программ на C/C++. Тем не менее, отличный вопрос! – Ben

ответ

6

Я совершенно уверен, что это будет почти невозможно осуществить это в Haskell, который по умолчанию подразумевает лень. Например, есть функции через стандартную библиотеку, которая была бы бесполезна на строгом языке, потому что на самом деле они гарантированно не прекращаются, если вы запрашиваете всю свою продукцию. Поэтому, если у вас есть «строгий поднабор» Haskell, было бы сложно взаимодействовать с любым другим кодом Haskell, и в любом случае вы бы действительно программировали на другом языке.

Кроме того, я думаю, что вы ищете не то место, думая о монадах. Data.STRef действительно не имеет ничего общего с тем, чтобы избежать толчков и лености. На самом деле то, что вы хотите (строгость), не имеет ничего общего с необходимостью, и вам не нужна изменчивость или монады, чтобы получить его. Существуют языки программирования, столь же чистые, как и Haskell, но строгие во всем мире. Например, я работал с Mercury.

+0

Я думал, что Меркурий не является строгим. Причина, по которой он строгий, должен проверять каждое условие для цели (состоящее из сочетания ','), в то время как с дизъюнкцией (';') он может выбирать. И, как я знаю, он может переупорядочить что-то, сохраняя 'di' /' uo' (уничтоженный вход/уникальный вывод, используемый для представления объектов, похожих на IO). Постскриптум Приятно знать, что есть еще один человек, который видел Меркурий и распространяет факт существующего такого рода. – ony

+0

@ony Disjunctions являются нестрогими только таким же образом, что если/then/else не является строгим на каждом языке; вы не можете иметь ветвление, если все ветви полностью оцениваются до того, как они будут выбраны! Язык, как правило, строгий, поскольку вызов 'предикат (Input, Output)' будет возвращаться с 'Output', привязанным к полностью оцениваемому значению (без thunks), и если' Output' бесконечен, вызов не будет завершен, даже если вы не нужно использовать 'Output' для чего-либо. – Ben

+0

@ony Это немного сложнее из-за отставания/недетерминизма, хотя в этом смысле вы правы. «предикат» может быть возобновлен, чтобы произвести другой «вывод», если что-то не удается на более высоком уровне, поэтому данные, на которые он ссылался, могут храниться в памяти в случае, если это произойдет. На самом деле это не то же самое, что безумное поколение из-за нестрогости в Haskell и, как правило, гораздо более интуитивно предсказуемо из класса детерминизма предикатов. – Ben

1

Возможно, вас заинтересует BangPatterns language extension и Unboxed types/operations. Но вы также должны знать, что любая функция всегда будет иметь значение типа boxed. Это ответственность за оптимизацию, чтобы исключить любые значения ref-kind, компилируя код в соответствии с «bangs» и другими функциями в функции.

1

Я думал об этом связку. Так есть другие люди:

Теперь мои собственные спекуляции.

В приведенных выше рассуждениях, основная идея большой части времени является то, что вы могли бы придерживаться ! на любого типа, чтобы получить строгую, определенно, оцененный WHNF версию этого типа. Таким образом, Int может быть громким, а !Int определенно не один. Это вызывает интересные вопросы для typechecker. Выполняется !Int ~ Int? Если это не так - два являются совершенно отдельными, несовместимыми типами, то работа с ними будет очень болезненной.С другой стороны, если он делает, то не будет ничего, чтобы предотвратить прохождение неоцененного Int, где ожидается !Int - в конце концов, они одного типа. То, что вы в идеале хотите, - это предоставить !Int, где ожидается Int, но не наоборот. Это звучит как отношение подтипа: !a <: a, !a - это подтип a, a, который населен как оцененными, так и неоцененными значениями (thunks), а !a - только оцененными. Система типа GHC не имеет подтипов и, вероятно, не может, поскольку другие части системы типов не взаимодействуют с ней хорошо. Это очень ограниченный конкретный экземпляр подтипирования: программист не может объявлять произвольные отношения подтипов, а существует одно жесткое кодирование: forall a. !a <: a. Я понятия не имею, делает ли это более разумным для реализации.

Предположим, это можно сделать. Вы получите дополнительные вопросы. Что делать, если вы попытаетесь поставить Int, где ожидается !Int? Ошибка типа? В идеале, я думаю, это не так, вместо этого компилятор будет вставлять код для его оценки и продолжать. Хорошо. Как насчет поставки [Int], где ожидается [!Int], или f a и f !a в общем случае? Как мог компилятор, возможно, знать, как пройти какую-либо данную структуру, чтобы найти те точки, где он содержит a, для оценки этих и только тех? Так будет что быть ошибки типа? Скажем так. Как программист выполняет преобразование вручную - получите f !a от f a? Возможно, путем сопоставления функции eval :: a -> !a? Но это бессмысленно. Хаскелл повсеместно ленив. Если вы примените его к аргументу, eval x, то до тех пор, пока его значение не понадобится, это будет бит. Таким образом, eval x не может иметь тип !a. Строчные аннотации в позиции возвращаемого типа не имеют никакого смысла. Итак, что насчет: data Wrap a = Wrap { unwrap :: a }, eval' :: a -> Wrap !a, с семантикой, что Wrap !a может быть ханком, но компилятор вставляет код, чтобы при его оценке !a внутри тоже определенно был бы оценен? Собственно, вот более простая формулировка: data Wrap' a = Wrap' { unwrap' :: !a } (eval' = Wrap'). Который является действующим юридическим Haskell, подпадающим под нашу новую строгую типизацию. Наверное, это хорошо. Но как только вы попытаетесь использовать его, вы снова получите проблемы. Как вы можете получить от f a до f !a, еще раз - fmap unwrap . fmap Wrap? Но unwrap имеет ту же проблему, что и eval. Таким образом, все это кажется не таким уж тривиальным. А как насчет кажущегося безобидного обратного случая: поставка f !a, где ожидается f a? Это работает? (Другими словами, f !a <: f a?) Это зависит от того, как a используется внутри f. Компилятор должен иметь знание ковариантных, контравариантных и инвариантных позиций аргументов типа - другое дело, связанное с подтипированием.

Это насколько я продумал. Кажется сложнее, чем кажется.

Еще одна интересная вещь. Возможно, вы слышали или не слышали о понятиях неперекрытых типов: типы, которые не заселены дном. Это, насколько я могу судить, то же самое, что и есть. Типы, которые гарантированно оцениваются в WHNF; типы, которые гарантированно не будут снизу. Нет разницы, не так ли? GHC на самом деле уже имеет кучу неперекрытых типов в качестве примитивов (Int# и т. Д.), Но они подключены (вы не можете добавлять новые), а также unboxed в дополнение к тому, чтобы быть развязанным, поэтому они имеют различный вид (# вместо *) и не может смешиваться с обычными типами. Принимая во внимание, что !a будет несимметричным, но коробчатым, типа *.Нераскрытые типы - это то, о чем я уже упоминал несколько раз в теоретико-типологических контекстах, поэтому, возможно, было проведено некоторое исследование того, что потребуется для их реализации более общим образом. Я еще не смотрел.