ghci - нетерпеливая компиляция в интерактивном режиме?

Следующий тип программы проверяет, указываю ли я его в командной строке (например, ghci file.hs):

import Data.Ratio
foo = let x = [1..]
          y = (1%2) + (head x)
       in y

Однако, если я введу его в интерактивном режиме, я получу ошибку типа:

Prelude> import Data.Ratio
Prelude Data.Ratio> let x = [1..]
Prelude Data.Ratio> let y = (1%2) + (head x)
<interactive>:1:23:
    Couldn't match expected type `Ratio a0' with actual type `Integer'

Кажется, что x охотно печатается как [Integer], в отличие от более общего (Num t, Enum t) => [t].

Могу ли я что-нибудь с этим поделать? Существуют ли другие ситуации, когда интерактивный режим будет отличаться от пакетного режима?


person ErikR    schedule 28.02.2012    source источник
comment
Действительно, это страшное ограничение мономорфизма. Есть два способа обойти это: дать явную подпись или отключить это ограничение (в GHCi вы можете сделать :set -XNoMonomorphismRestriction и все готово; языковые прагмы и флаги компилятора также работают).   -  person Vitus    schedule 29.02.2012


Ответы (2)


Привязки без аргументов, т. е. привязки формы x = ..., подлежат ограничению мономорфизма, что означает, что GHC попытается сделать его неполиморфным, используя любую доступную информацию о типах, и вернётся к введите значение по умолчанию для разрешения любых неясностей. (На самом деле GHCi использует слегка более разрешающий набор правил по умолчанию, но это не имеет большого значения для этого вопроса).

Поскольку при написании let x = [1..] нет другой доступной информации о типе, тип по умолчанию приводит к тому, что тип выводится как [Integer], поскольку Integer является числовым типом по умолчанию.

Есть несколько способов решить эту проблему:

  1. Используйте сигнатуру типа. Это работает всегда, но иногда может быть утомительным при работе со сложными типами.

    > let x = [1..] :: [Rational]
    
  2. Запишите привязку с аргументами. Это неприменимо в вашем случае, но иногда вы сталкиваетесь с этой проблемой при написании определений бесточечных функций.

    > let f = (+)
    > :t f
    f :: Integer -> Integer -> Integer
    > let f x y = x + y
    > :t f
    f :: Num a => a -> a -> a
    
  3. Дайте средству проверки типов больше информации. В вашем случае мы могли бы избежать проблемы, написав обе привязки в одном операторе let. Затем GHC может использовать информацию о типе из второй привязки, чтобы правильно сделать вывод, что x должен иметь тип [Rational].

    > let x = [1..]; y = 1%2 + head x
    > :t x
    x :: [Ratio Integer]
    
  4. Отключите ограничение мономорфизма. Это может иметь серьезные последствия для производительности, если вы ожидаете, что тип чего-либо будет, например, Integer, в то время как на самом деле это Num a => a, так как последний должен пересчитываться каждый раз, пока первый может использоваться совместно. Это основная причина, по которой ограничение существует в первую очередь.

    Однако в интерпретаторе это обычно менее проблематично, поэтому удобство часто того стоит.

    > :set -XNoMonomorphismRestriction
    > let x = [1..]
    > :t x
    x :: (Num t, Enum t) => [t]
    

    Если вы хотите, чтобы это было включено по умолчанию, вы можете добавить его в свой .ghci файл.

person hammar    schedule 28.02.2012

Вы можете что-то с этим сделать, определив x следующим образом:

let x = [1..] :: [Ratio Int]

as in:

Data.Ratio Prelude> let x = [1..] :: [Ratio Int]
Data.Ratio Prelude> let y = (1%2) + (head x)
Data.Ratio Prelude> y
3 % 2
Data.Ratio Prelude>
person indygemma    schedule 28.02.2012
comment
Я настоятельно рекомендую не использовать Ratio Int, если вы абсолютно точно не знаете, что ваши вычисления не могут переполниться, и вы отчаянно нуждаетесь в производительности - и даже тогда мне это не нравится. На мой взгляд, было большой ошибкой делать Ratio полиморфным типом, даже небольшие вычисления могут запросто превысить 64 бита, и тогда получится полная ерунда. - person Daniel Fischer; 29.02.2012