Почему 3 и x (которые были назначены 3) имеют разные предполагаемые типы в Haskell?

Тип вывода в Haskell имеет немного кривой обучения (если не сказать больше!). Хороший способ начать обучение – это простые примеры. Итак, следующее представляет собой «мир привет» для вывода типа.

Рассмотрим следующий пример:

Prelude> :t 3 3 :: (Num t) => t Prelude> let x = 3 Prelude> :tx x :: Integer 

Возникает вопрос: почему 3 и x имеют разные типы?

Ссылка:

Прочтите ответы ниже для полной истории; вот просто сводка ссылок:

  1. Тип GHC по умолчанию: раздел отчета Haskell 4.3.4
  2. Расширенные функции GHCi по умолчанию: использование раздела GHCi 2.4.5
  3. Мономорфное ограничение: Haskell wiki

    Здесь есть еще один фактор, упомянутый в некоторых ссылках, которые включает в себя acfoltzer, но это может быть полезно сделать здесь. Вы сталкиваетесь с эффектом ограничения monoморфизма . Когда ты говоришь

     let x = 5 

    вы делаете определение переменной на верхнем уровне . MR настаивает на том, что такие определения, в противном случае несопровождаемые сигнатурой типа, должны быть специализированы для monoморфного значения, выбирая (надеюсь) подходящие экземпляры по умолчанию для неразрешенных переменных типа. В отличие от этого, когда вы используете :t для запроса выведенного типа, такое ограничение или дефолт не налагаются. Так

     > :t 3 3 :: (Num t) => t 

    потому что 3 действительно перегружен: он допускается любым числовым типом. Правила по умолчанию выбирают Integer как числовой тип по умолчанию, поэтому

     > let x = 3 > :tx x :: Integer 

    Но теперь давайте выключим МР.

     > :set -XNoMonomorphismRestriction > let y = 3 > :ty y :: (Num t) => t 

    Без MR определение так же полиморфно, как и может быть, так же, как и перегруженное как 3 . Просто проверяю…

     > :ty * (2.5 :: Float) y * (2.5 :: Float) :: Float > :ty * (3 :: Int) y * (3 :: Int) :: Int 

    Обратите внимание, что полиморфный y = 3 по-разному специализируется в этих целях в соответствии с методом fromInteger поставляемым с соответствующим экземпляром Num . То есть y не связано с конкретным представлением 3 , а скорее схемой построения представлений 3 . Naïvely составлен, это рецепт для медленного, который некоторые люди называют мотивацией для MR.

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

    Когда вы пытаетесь узнать, как работает система типов, очень полезно отделить аспекты вывода типа, которые

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

    2. «угадать план», обобщая типы, чтобы назначить схему полиморфного типа определению без подписи типа: это довольно хрупко, и чем больше вы проходите мимо основной дисциплины Хиндли-Милнера с classами classов с polymorphismом более высокого ранга, GADT, странные вещи становятся.

    Хорошо узнать, как работает первый, и понять, почему второй трудный. Большая часть странности в типе вывода связана со вторым, и с эвристикой, как ограничение monoморфизма, пытающееся доставить полезное поведение по умолчанию перед лицом двусмысленности.

    Это происходит из-за того, что по умолчанию в GHCi используется тип , как здесь описано здесь , здесь и здесь , среди прочих. К сожалению, это похоже на то, что трудно найти, так как есть много способов описать это поведение, прежде чем вы узнаете фразу «type defaulting».

    Обновление : D’oh. Убрали плохой пример.

    Поскольку никто не упомянул, почему существует ограничение monoморфизма, я подумал, что добавлю этот бит из «Истории Хаскелла»: «Lazy With Class» .

    6.2 Ограничение monoморфизма Основным источником споров на ранних этапах было так называемое «ограничение monoморфизма». Предположим, что genericLength имеет этот перегруженный тип:

     genericLength :: Num a => [b] -> a 

    Теперь рассмотрим это определение:

     f xs = (len, len) where len = genericLength xs 

    Похоже, что len следует вычислять только один раз, но его можно фактически вычислить дважды. Зачем? Потому что мы можем вывести тип len :: (Num a) => a ; когда desugared с переводом словаря, len становится функцией, которая вызывается один раз для каждого вхождения len , каждый из которых может использоваться для другого типа.

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

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

    Ограничение monoморфизма является явно бородавкой на языке. Кажется, он кусает каждого нового программиста Haskell, создавая неожиданное или неясное сообщение об ошибке. Было много обсуждений альтернатив. Компилятор Glasgow Haskell (GHC, раздел 9.1) обеспечивает флагов:

     -fno-monomorphism-restriction 

    для полного ограничения этого ограничения. Но за все это время не было удовлетворительной альтернативы.

    Я считаю, что тон статьи в отношении ограничения monoморфизма очень интересен.

    Давайте будем гением компьютера.