Haskell에서 평가되는 함수에 대해 어떤 규칙이 있습니까?


12

제목에서 알 수 있듯이 Haskell 함수 반환 단위를 평가하려면 어떤 보증이 있습니까? 그러한 경우에는 어떤 종류의 평가도 실행할 필요가 없다고 생각할 것입니다. ()엄격한 요구가 명시되어 있지 않으면 컴파일러는 그러한 모든 호출을 즉시 값으로 바꿀 수 있습니다. 반품 ()또는 하단.
나는 GHCi에서 이것을 실험했고, 그 반대의 일, 즉 그러한 기능이 평가 된 것처럼 보입니다. 매우 원시적 인 예는

f :: a -> ()
f _ = undefined

f 1의 존재로 평가 하면 오류가 발생 undefined하므로 일부 평가는 확실히 발생합니다. 그러나 평가가 얼마나 깊이 진행되는지는 확실하지 않습니다. 때로는 함수를 호출하는 모든 호출을 평가하는 데 필요한만큼 깊이있는 것처럼 보입니다 (). 예:

g :: [a] -> ()
g [] = ()
g (_:xs) = g xs

이 코드는로 표시되면 무한정 반복됩니다 g (let x = 1:x in x). 하지만

f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()

h (f 1)returns 를 표시하는 데 사용할 수 ()있으므로이 경우 모든 단위 값 하위 표현식이 평가되는 것은 아닙니다. 여기서 일반적인 규칙은 무엇입니까?

ETA : 물론 게으름에 대해 알고 있습니다. 컴파일러 작성자 가이 특별한 경우를 평소보다 더 게으르게 만드는 것을 막고 있습니다.

ETA2 : 예제 요약 : GHC는 ()다른 유형과 똑같이 취급 하는 것으로 보입니다 . 즉, 유형에 거주하는 규칙적인 값이 함수에서 반환되어야하는 문제가있는 것처럼 보입니다 . 그러한 값이 하나만 있다는 사실은 최적화 알고리즘에 의해 사용되지 않는 것 같습니다.

ETA3 : Haskell을 말할 때, 나는 Haskell-H-in-GHC가 아니라 보고서에 의해 정의 된 Haskell을 의미합니다. 내가 상상했던 것 ( '독자의 100 %에 의한 것')만큼 널리 공유되지 않은 가정 인 것 같거나, 아마도 더 명확한 질문을 공식화 할 수 있었을 것이다. 그럼에도 불구하고, 나는 원래 그 함수가 호출되는 것에 대해 어떤 보증 이 있는지 묻기 때문에 질문의 제목을 바꾸는 것을 후회합니다 .

ETA4 :이 질문에 대한 답이 나오고있는 것 같습니다. ( '닫는 질문'기능을 찾고 있었지만 '자신의 질문에 대한 답변'만 찾았으며 답을 찾을 수 없으므로 해당 경로를 따라 가지 않았습니다.) 아무도 보고서에서 어떤 방법 으로든 결정하지 않은 것을 제기하지 않았습니다. 나는 강력하지만 확실하지 않은 '언어를 보장하지 않습니다'라는 대답으로 해석하고 싶어합니다. 우리가 아는 것은 현재 GHC 구현이 그러한 기능의 평가를 생략하지 않는다는 것입니다.

OCaml 앱을 Haskell에 이식 할 때 실제 문제가 발생했습니다. 원래 앱에는 여러 유형의 상호 재귀 구조가 있었고 코드는 assert_structureN_is_correct1..6 또는 7에서 N을 호출하는 여러 함수를 선언했습니다 . 각 함수 는 구조가 실제로 정확하면 단위를 반환하고 그렇지 않은 경우 예외를 던졌습니다. . 또한 이러한 기능은 정확성 조건을 분해 할 때 서로 호출되었습니다. Haskell에서는 Either String모나드를 사용하여 처리하는 것이 더 좋았 으므로 필자는 그렇게했지만 이론적 인 문제로 남아 있습니다. 모든 의견과 답변에 감사드립니다.


1
이것은 직장에서의 게으름입니다. 함수의 결과가 요구되지 않는 한 (예 : 생성자에 대한 패턴 일치 등) 함수의 본문은 평가되지 않습니다. 차이를 관찰, 비교하려고 h1::()->() ; h1 () = ()하고 h2::()->() ; h2 _ = (). 모두를 실행 h1 (f 1)하고 h2 (f 1), 그 첫 번째 하나의 요구 사항을 참조하십시오 (f 1).
chi

1
"Laziness는 어떤 종류의 평가도 수행하지 않고 ()로 대체되도록 지시하는 것 같습니다." 그게 무슨 뜻이야? 모든 경우에 f 1의해 "대체"됩니다 undefined.
oisdk

3
함수 ... -> ()는 1) 종료 및 리턴 (), 2) 예외 / 런타임 오류로 종료하고 아무것도 리턴하지 못하거나 3) 분기 (무한 재귀) 할 수 있습니다. GHC는 1) 발생할 수 있다고 가정하여 코드를 최적화하지 않습니다. f 1요구되는 경우 평가 및 리턴을 건너 뛰지 않습니다 (). Haskell 시맨틱은이를 평가하고 1,2,3 사이에 어떤 일이 발생하는지 확인하는 것입니다.
chi

2
()이 질문에는 (유형이나 값) 특별한 것이 없습니다 . () :: ()예를 들어 0 :: Int어디에서나 교체하면 동일한 결과가 나타납니다 . 이것들은 모두 게으른 평가의 오래된 지루한 결과입니다.
Daniel Wagner

2
아니, "피하는 것"등은 하스켈 의미론이 아닙니다. 하고있다 개의 가능 값 ()유형, ()undefined.
윌 네스

답변:


10

유형에 ()가능한 값이 하나뿐 이라는 가정에서 비롯된 ()것이므로 유형 값을 리턴하는 모든 함수 호출 ()이 실제로 값을 생성한다고 가정해야합니다 ().

이것은 Haskell의 작동 방식이 아닙니다. 모든 유형에는 Haskell에 값이 하나 더 있습니다. 즉 , 값, 오류 또는 "bottom"이 없습니다 undefined. 따라서 실제로 평가가 이루어지고 있습니다.

main = print (f 1)

핵심 언어의

main = _Case (f 1) _Of x -> print x   -- pseudocode illustration

또는 짝수 (*)

main = _Case (f 1) _Of x -> putStr "()"

코어 _Case는 다음과 같이 강제하고 있습니다 .

" %case[식]을 평가하면 테스트중인 표현식 ("스크 루틴 ")을 강제로 평가합니다. 스크 루틴 값은 %of키워드 다음에 나오는 변수에 묶입니다 ...".

값은 머리 정상 형태를 약하게 만듭니다. 이것은 언어 정의의 일부입니다.

하스켈은 하지 선언적 프로그래밍 언어.


(*) print x = putStr (show x)show () = "()"이므로 show호출을 모두 컴파일 할 수 있습니다.

그 값은 실제로로 알려져 있으며 (), 심지어 그 값 show ()도로 알려져 있습니다 "()". 여전히 받아 들여진 Haskell 시맨틱 (f 1)은 사전에 알려진 문자열을 인쇄하기 전에 값이 약한 머리 정상적인 형태로 강요되도록 요구합니다 "()".


편집 : 고려하십시오 concat (repeat []). 그것이 있어야합니까 [], 아니면 무한 루프 여야합니까?

이것에 대한 "선언적 언어"의 대답은 아마도입니다 []. Haskell의 대답은 무한 루프 입니다.

게으름 "가난한 사람의 선언적 프로그래밍"이지만 여전히 실제 는 아닙니다 .

edit2 : 강제 print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()로만 ; 그 정의에 따르면 답 을 만들기 위해 아무것도 강요 할 필요가 없습니다 .hfhh _ = ()

이별 설명 : 게으름은 raison d' etre를 가질 수 있지만 정의는 아닙니다. 게으름이 바로 그것입니다. 그것은 모든 값이 초기에 썽크 (thunk)가되는 것으로 정의되며, 이는 요구에 따라 WHNF로 강제된다 main. 특정 상황에 따라 특정 사례에서 바닥을 피하는 데 도움이된다면 그렇게합니다. 그렇지 않다면 아닙니다. 그게 다야

좋아하는 언어로 직접 구현하여 느낌을 얻을 수 있습니다. 그러나 모든 중간 값 을 신중하게 명명 함으로써 표현의 평가를 추적 할 수도 있습니다 .


보고서로 가면 , 우리는

f :: a -> ()
f = \_ -> (undefined :: ())

그때

print (f 1)
 = print ((\ _ ->  undefined :: ()) 1)
 = print          (undefined :: ())
 = putStrLn (show (undefined :: ()))

instance Show () where
    show :: () -> String
    show x = case x of () -> "()"

계속된다

 = putStrLn (case (undefined :: ()) of () -> "()")

이제 보고서 의 패턴 일치 에 대한 3.17.3 절의 형식적 의미

case표현 의 의미론은 [그림 3.1–3.3]에 주어진다. 이러한 ID가 [...]을 갖도록 모든 구현이 작동해야합니다.

및 경우 (r)도 3.2 미국

(r)     case  of { K x1  xn -> e; _ -> e } =  
        where K is a data constructor of arity n 

() arity 0의 데이터 생성자이므로

(r)     case  of { () -> e; _ -> e } =  

따라서 전체 평가 결과는 다음과 같습니다 .


2
나는 당신의 설명을 좋아합니다. 명확하고 간단합니다.
arrowed

@DanielWagner case실제로 Core 에서 생각하고 간격 구멍을 무시하고있었습니다. :) 나는 Core를 언급하기 위해 편집했다.
윌 네스

1
show의해 강제 실행 되지 print않습니까? 같은 뭔가show x = case x of () -> "()"
user253751

1
나는 caseHaskell 자체가 아닌 Core에서 언급 합니다. Haskell은 caseAFAIK를 사용 하는 Core로 변환됩니다 . case하스켈에서 자체적으로 강제하지 않는 것이 맞습니다 . Scheme 또는 ML (ML을 쓸 수 있다면) 또는 의사 코드로 무언가를 쓸 수 있습니다.
Will Ness

1
이 모든 것에 대한 정식 답변은 아마도 보고서 어딘가에있을 것입니다. 내가 아는 전부는 여기에 "최적화"가 없으며 "정규적 가치"는이 맥락에서 의미있는 용어가 아닙니다. 강요된 것은 무엇이든간에. print인쇄하는 데 필요한만큼 힘을줍니다. 유형을 보지 않고 프로그램이 실행될 때 유형이 사라지고 지워집니다. 유형에 따라 컴파일 타임에 올바른 인쇄 서브 루틴이 이미 선택되어 컴파일됩니다. 해당 서브 루틴은 런타임시 입력 값을 WHNF로 계속 강제하고, 정의되지 않은 경우 오류가 발생합니다.
윌 네스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.