Подсчет инверсий: StackOverflowError во Фреге, отлично работает в Haskell

Я пытаюсь подсчитать инверсии для списка чисел. Следующая программа Фреге работает для небольшого набора чисел, но выдает StackOverflowError для 100000 чисел.

import frege.IO

inversionCount [] _ = (0, [])
inversionCount [x] _ = (0, [x])
inversionCount xs n = (count, sorted) where
  count = lcount + rcount + mergecount
  (lcount, lsorted) = inversionCount left lsize
  (rcount, rsorted) = inversionCount right rsize
  (mergecount, sorted) = inversionMergeCount lsorted lsize rsorted rsize (0, [])
  (left, right) = splitAt mid xs
  mid = n `quot` 2
  lsize = mid
  rsize = n - mid

inversionMergeCount xs _ [] _ (acc,sorted) = (acc, reverse sorted ++ xs)
inversionMergeCount [] _ ys _ (acc,sorted) = (acc, reverse sorted ++ ys)
inversionMergeCount (xs@(x:restx)) m (ys@(y:resty)) n (acc, sorted)
  | x < y = inversionMergeCount restx (m - 1) ys n (acc, x:sorted)
  | x > y = inversionMergeCount xs m resty (n - 1) (acc + m, y:sorted)
  | otherwise = inversionMergeCount restx (m - 1) resty (n - 1) (acc, x:y:sorted)

main (fileName:_) = do
  input <- readFile fileName
  let xs = map atoi (lines input)
  println . fst $ inversionCount xs 100000

--Haskell's readFile using Java's Scanner
type JScanner = JScannerT RealWorld

data JScannerT s = native java.util.Scanner where
  native new :: File -> ST s (Exception JScanner)
  native useDelimiter :: JScanner -> String -> ST s JScanner
  native next :: JScanner -> ST s String

readFile f = do
  file <- File.new f
  exceptionScanner <- JScanner.new file
  let scanner = either throw id exceptionScanner
  scanner.useDelimiter "\\Z"
  scanner.next

Тот же код на Haskell работает нормально:

import System.Environment

inversionCount [] _ = (0, [])
inversionCount [x] _ = (0, [x])
inversionCount xs n = (count, sorted) where
  count = lcount + rcount + mergecount
  (lcount, lsorted) = inversionCount left lsize
  (rcount, rsorted) = inversionCount right rsize
  (mergecount, sorted) = inversionMergeCount lsorted lsize rsorted rsize (0, [])
  (left, right) = splitAt mid xs
  mid = n `quot` 2
  lsize = mid
  rsize = n - mid

inversionMergeCount xs _ [] _ (acc,sorted) = (acc, reverse sorted ++ xs)
inversionMergeCount [] _ ys _ (acc,sorted) = (acc, reverse sorted ++ ys)
inversionMergeCount (xs@(x:restx)) m (ys@(y:resty)) n (acc, sorted)
  | x < y = inversionMergeCount restx (m - 1) ys n (acc, x:sorted)
  | x > y = inversionMergeCount xs m resty (n - 1) (acc + m, y:sorted)
  | otherwise = inversionMergeCount restx (m - 1) resty (n - 1) (acc, x:y:sorted)


main = do
  (fileName: _) <- getArgs
  contents <- readFile fileName
  let xs :: [Int]
      xs = map read (lines contents)
  print . fst $ inversionCount xs 100000

Что может быть причиной переполнения стека? Это какая-то функция, не являющаяся хвостовой рекурсией?


person Marimuthu Madasamy    schedule 19.02.2013    source источник
comment
Похоже, вы строите множество переходников в inversionMergeCount по выражениям reverse sorted ++ xs и reverse sorted ++ ys. Попробуйте заключить в let rs = reverse sorted in rs seq`xs seq(rs++xs)`и т.д.   -  person David Unric    schedule 20.02.2013
comment
Возможно, это связано с тем, что тип String строг во Фреге, но ленив в Haskell?   -  person MathematicalOrchid    schedule 20.02.2013
comment
@MarimuthuMadasamy Это напоминает мне, что компилятор должен предупреждать о ленивых аргументах в хвостовых рекурсивных функциях, которые обновляются нетривиальным выражением. Попробуйте написать !m в третьем уравнении inversionMergeCount. Кроме того, второй аргумент inversionCount должен быть строгим.   -  person Ingo    schedule 20.02.2013


Ответы (2)


В Haskell, скорее всего, анализатор строгости лучше, или хвостовая рекурсия реализована по-другому, или среда выполнения просто имеет больше доступного стека.

Первое, что я бы попробовал, это установить -Xss8m или даже 16m.

Если это не помогает, имейте в виду, что ленивые аргументы, которые обновляются с применением строгих функций, таких как (-), (+) и т. д., создают переходники, которые иногда позже придется оценивать сразу. Это та же проблема, что и с foldl, и похоже, что второй аргумент inversionMergeCount и inversionCount страдает от этого.

Компилятор Frege должен предупредить об этом, если он увидит это, но на данный момент этого не происходит.

Другой вопрос, почему вы передаете последние 2 аргумента в кортежах? Вы также можете сделать акк строгим.

person Ingo    schedule 19.02.2013
comment
Спасибо Инго! Теперь он работает после того, как я сделал acc и m строгими. Я также удалил ненужный кортеж. - person Marimuthu Madasamy; 20.02.2013

По предложению Инго я внес изменения, и теперь код работает. Мне также пришлось сделать счет как Integer вместо Int, чтобы избежать переполнения int. Вот обновленный код со строгими аннотациями и преобразованием Integer:

inversionCount [] _ = (zero, [])
inversionCount [x] _ = (zero, [x])
inversionCount xs n = (count, sorted) where
  count = lcount + rcount + mergecount
  (lcount, lsorted) = inversionCount left lsize
  (rcount, rsorted) = inversionCount right rsize
  (mergecount, sorted) = inversionMergeCount lsorted lsize rsorted rsize zero []
  (left, right) = splitAt mid xs
  mid = n `quot` 2
  lsize = mid
  rsize = n - mid


inversionMergeCount xs _ [] _ !acc sorted = (acc, reverse sorted ++ xs)
inversionMergeCount [] _ ys _ !acc sorted = (acc, reverse sorted ++ ys)
inversionMergeCount (xs@(x:restx)) !m (ys@(y:resty)) n !acc sorted
  | x < y = inversionMergeCount restx (pred m) ys n acc (x:sorted)
  | x > y = inversionMergeCount xs m resty (pred n) (acc + m.big) (y:sorted)
  | otherwise = inversionMergeCount restx (pred m) resty (pred n) acc (x:y:sorted)
person Marimuthu Madasamy    schedule 20.02.2013
comment
Поскольку у вас одна и та же программа на Фреге и Хаскеле на одной машине с одними и теми же данными - хотелось бы узнать, чем отличается время выполнения, т.е. насколько быстрее оптимизирован Хаскель? - person Ingo; 20.02.2013
comment
В среднем на 5 прогонов Frege уходит 2,56 с, а оптимизированный Haskell — 1,34 с. - person Marimuthu Madasamy; 20.02.2013
comment
Эй, это не так уж плохо. В конце концов, за 2,56 секунды JIT почти ничего не сделал. - person Ingo; 20.02.2013
comment
Да, держать JVM в тепле какое-то время может сделать статистику интересной! - person Marimuthu Madasamy; 20.02.2013