2013-10-17 7 views
3

내 도구 상자에 Haskell을 추가하여 Real World Haskell을 통해 작업하고 있습니다. 처리hGetContents는 어떻게 메모리 효율성을 달성합니까?

공지 사항 hGetContents을 것을 : 저자가 말을 계속이 코드 샘플에 따라

import System.IO 
import Data.Char(toUpper) 

main :: IO() 
main = do 
    inh <- openFile "input.txt" ReadMode 
    outh <- openFile "output.txt" WriteMode 
    inpStr <- hGetContents inh 
    let result = processData inpStr 
    hPutStr outh result 
    hClose inh 
    hClose outh 

processData :: String -> String 
processData = map toUpper 

: the section on hGetContents의 입력과 출력의 장에서

는,이 예를 건너 왔어요 우리 모두를위한 독서. 또한 processData을 살펴보십시오. 그것은 부작용이 없으며 호출 될 때마다 항상 동일한 결과를 반환하기 때문에 순수한 함수입니다. 을 알 필요가 없으며을 입력 할 수 없습니다.이 경우 입력이 파일에서 지연적으로 읽히고 있습니다. 디스크의 20 문자 리터럴 또는 500GB 데이터 덤프로 완벽하게 작동 할 수 있습니다.

내 질문은 (NB 강조는 내 꺼야) : hGetContents을 수행 또는 그 결과 값이 메모리 효율성없이 달성하는 방법 -이 예에서는 - processData은 "말할 수있는"여전히 모든 혜택을 유지하는 순수 코드 (즉, processData), 특히 메모로 생성됩니까?

<- hGetContents inh

그렇게 inpStrprocessData가 받아들이는 정확히 유형 유형 String의 값에 바인딩 된 문자열을 반환합니다. 그러나 Real World Haskell의 저자를 올바르게 이해한다면이 문자열은 다른 문자열과 완전히 같지 않습니다. 메모리에 완전히로드되지 않습니다 (또는 완전히 평가되지 않은 문자열이있는 경우 완전히 평가됩니다.). .) processData로 전화를 걸 때까지.

따라서, 내 질문을하는 또 다른 방법은 다음과 같습니다 inpStr가 완전히 processData 호출시 메모리로 평가되거나로드되지 않는 경우, 다음 방법이없이, processData에 memoized 호출이있는 경우 조회 할 수 있습니다 먼저 완전히 inpStr을 평가합니까?

String 유형의 인스턴스가 각각 다르게 동작하지만이 추상화 수준에서 구분할 수 없습니까?

답변

4

hGetContents으로 반환되며 다른 하스켈 문자열과 다를 바 없습니다. 일반적으로 Haskell 데이터는 프로그래머가 코드를 확인하기 위해 별도의 조치를 취하지 않으면 (예 : seq, deepseq, 강타 패턴) 완전히 평가되지는 않습니다.

문자열은 (기본적으로)

data List a = Nil | Cons a (List a) -- Nil === [], Cons === : 
type String = List Char 

이 문자열이 비어 또는 단일 문자 (머리)와 다른 문자열 (꼬리) 중 하나 있음을 의미로 정의됩니다. laziness로 인해 꼬리가 메모리에 존재하지 않을 수 있으며 무한 할 수도 있습니다. String을 처리 할 때 Haskell 프로그램은 일반적으로 Nil 또는 Cons인지 확인한 다음 필요에 따라 분기하고 진행합니다. 함수가 꼬리를 평가할 필요가 없다면, 함수는 그렇지 않습니다.이것은 완벽하게 합법적 인 문자열 무한

allA's = repeat 'a' :: String 

safeHead :: String -> Maybe Char 
safeHead [] = Nothing 
safeHead (x:_) = Just x 

입니다 :이 기능은 예를 들어, 초기 생성자를 확인해야합니다. 이 문자열을 현명하게 조작 할 수 있지만 모든 내용을 인쇄하거나 길이를 계산하거나 프로그램을 종료하지 않는 무제한 순회를 수행하려고하면이 문자열을 현명하게 조작 할 수 있습니다. 그러나 아무런 문제없이 safeHead과 같은 함수를 사용할 수 있으며 일부 초기 부분 문자열을 소비 할 수도 있습니다.

그러나 이상한 일이 일어나고있는 직감은 정확합니다. hGetContentsunsafeInterleaveIO 특수 함수를 사용하여 구현되며, 이는 기본적으로 IO 동작의 컴파일러 훅입니다. 이것에 관해서는 더 적은 것이 더 좋을수록 좋습니다.

인수가 완전히 평가되지 않은 상태에서 함수에 대한 메모가있는 호출이 있는지 확인하는 것이 맞습니다. 그러나 대부분의 컴파일러는이 최적화를 수행하지 않습니다. 문제는 컴파일러가 호출을 메모하는 것이 언제 가치가 있는지를 판단하는 것이 매우 어렵다는 것입니다. 그렇게함으로써 모든 메모리를 소비하기가 매우 쉽습니다. 다행히 적절한 경우 메모 작성을 추가하는 데 사용할 수있는 several memoizing libraries이 있습니다.

+0

답변 해 주셔서 감사합니다. 호기심을 만족시키기 위해 GHC에서 메모 작성을 수행하는지 또는 방금 언급 한 이유로 최적화를 수행하지 않는지 알려주시겠습니까? 원자적인 인수 (즉, 목록과 같은 데이터 구조가 없음)가있는 함수를 메모하는 것이 구현하기가 훨씬 쉽다는 것을 알 것입니다. – Marcel

+2

GHC는 함수의 자동 메모를 수행하지 않습니다. 그러나 변수의 값은 한 번만 계산됩니다. – kqr

+0

@MarcelOomens : 일반적으로 대답하기는 어렵지만 간단한 열거 형의 분기 (예 :'data Foo = Foo | Bar | Baz')는 종종 메모 된 호출로 바뀝니다. 이것은 일반적인 GHC 최적화 변환의 결과 인 것 같습니다. 특별한 지원이 있다고 생각하지 않습니다. –