Haskell의 엄격함은 무엇입니까?


90

우리 모두는 Haskell이 기본적으로 게으르다는 것을 알고 있습니다 (또는 알아야합니다). 평가할 때까지 아무것도 평가되지 않습니다. 그렇다면 언제 무언가를 평가해야합니까? Haskell이 엄격해야하는 점이 있습니다. 이 특정 용어가 내가 생각했던 것만 큼 널리 퍼지지는 않았지만 나는 이것을 "엄격 점"이라고 부른다. 나에 따라:

Haskell의 감소 (또는 평가) 는 엄격 성 지점 에서만 발생합니다.

질문은 그래서 : 무엇을 정확하게 , 하스켈의 엄격 포인트입니까? 내 직감에 따르면 main, seq/ 뱅 패턴, 패턴 매칭 및을 IO통해 수행되는 모든 작업 main이 주요 엄격 점이지만 그 이유를 실제로 알지 못합니다.

(그들은 "엄격 포인트"라고하지 않는 경우 또한, 무엇 이다 그들이라고?)

좋은 대답에는 WHNF 등에 대한 토론이 포함될 것이라고 생각합니다. 나는 또한 람다 미적분에 영향을 미칠 것이라고 상상합니다.


편집 :이 질문에 대한 추가 생각.

이 질문에 대해 생각해 보았을 때 엄격 점의 정의에 무언가를 추가하는 것이 더 분명하다고 생각합니다. Strictness points는 다양한 컨텍스트 와 다양한 깊이 (또는 엄격함)를 가질 수 있습니다 . "하스켈에서의 감소는 엄격 성 지점에서만 발생한다"라는 저의 정의로 돌아가서,이 정의에 다음 절을 추가하겠습니다. "엄격 성 지점은 주변 컨텍스트가 평가되거나 축소 될 때만 트리거됩니다."

그래서 제가 원하는 종류의 답변을 시작하도록하겠습니다. main엄격한 포인트입니다. 특히 문맥의 주요 엄격 점 인 프로그램으로 지정됩니다. 프로그램 ( main의 컨텍스트)이 평가되면 main의 엄격 성 포인트가 활성화됩니다. Main의 깊이는 최대이며 완전히 평가되어야합니다. Main은 일반적으로 IO 작업으로 구성되며, 이는 컨텍스트가 main.

이제 시도해보십시오. seq이러한 용어에 대해 논의 하고 패턴 매칭을 수행하십시오. 함수 적용의 뉘앙스를 설명하십시오 : 얼마나 엄격합니까? 어때요? 어때 deepseq? letcase진술? unsafePerformIO? Debug.Trace? 최상위 수준 정의? 엄격한 데이터 유형? 뱅 패턴? Etc. 이러한 항목 중 몇 개를 시퀀스 또는 패턴 매칭으로 설명 할 수 있습니까?


10
직관적 인 목록은 아마도 직교하지 않을 것입니다. 나는 seq및 패턴 매칭이 충분하다고 생각 하며 나머지는 그것들과 관련하여 정의됩니다. IO예를 들어 패턴 매칭은 행동 의 척추 엄격함을 보장한다고 생각 합니다.
CA McCann 2011 년

+내장 숫자 유형 과 같은 프리미티브 도 엄격함을 강요하며 순수한 FFI 호출에도 동일하게 적용된다고 가정합니다.
hammar

4
여기에는 두 가지 개념이 혼동되는 것 같습니다. 패턴 일치와 seq 및 bang 패턴은 표현식이 하위 표현식에서 엄격해질 수있는 방법입니다. 즉, 최상위 표현식이 평가되면 하위 표현식도 마찬가지입니다. 반면에 주요 수행 IO 작업은 평가 시작 방법 입니다. 이것들은 다른 것들이며 같은 목록에 포함시키는 것은 일종의 유형 오류입니다.
Chris Smith

@ChrisSmith 저는이 두 가지 다른 경우를 혼동하지 않으려 고합니다. 그들이 상호 작용하는 방법에 대한 추가 설명을 요청하는 것이 있다면. 엄격함은 어떻게해서 든 발생하며, 두 경우 모두 중요하지만 엄격함이 "일어남"의 일부입니다. (및 @ monadic : ಠ_ಠ)
Dan Burton

전체 답변을 시도하지 않고이 질문의 측면을 논의 할 공간이 필요하거나 필요하다면이 질문에 대한 내 / r / haskell 게시물
Dan Burton

답변:


46

시작하기 좋은 곳은이 논문을 이해하는 것입니다 : Lazy Evalution을위한 Natural Semantics (Launchbury). 이는 GHC의 Core와 유사한 작은 언어에 대해 표현식이 평가되는시기를 알려줍니다. 나머지 질문은 전체 Haskell을 Core에 매핑하는 방법이며, 대부분의 번역은 Haskell 보고서 자체에 의해 제공됩니다. GHC에서 우리는이 과정을 "desugaring"이라고 부릅니다. 그 이유는 문법적 설탕을 제거하기 때문입니다.

글쎄요, 그게 전체 이야기가 아닙니다. 일찍이). 그래서 정말 방법을 이해하는 당신 , 당신은 코어 GHC에 의해 생산 볼 필요가 프로그램 평가됩니다.

아마도이 대답은 당신에게 약간 추상적 인 것처럼 보이지만 (나는 특별히 뱅 패턴이나 seq를 언급하지 않았습니다), 당신은 정확한 것을 요청 했고 이것은 우리가 할 수있는 최선에 대한 것입니다.


18
나는 GHC가 "desugaring"이라고 부르는 것에서 제거되는 구문 설탕이 Haskell 언어 자체실제 구문을 포함한다는 사실이 항상 흥미 롭다는 사실을 발견했습니다. GHC가 실제로 GHC를위한 최적화 컴파일러라는 것을 암시 합니다 . 우연히도 Haskell을 Core로 번역하기위한 매우 정교한 프런트 엔드 를 포함하는 핵심 언어 . :]
CA McCann 2011 년

그러나 타입 시스템은 정확하게 작동하지 않습니다. 그리고 내가 이해하는 모든 최신 TF / GADT 항목은 그 격차를 여전히 더 넓게 만들었습니다.
sclv

1
GCC는 C도 최적화하지 않습니다 : gcc.gnu.org/onlinedocs/gccint/Passes.html#Passes
György Andrasek

20

나는 아마도이 질문을 Haskell이 어떤 상황에서 표현을 평가할 것인가? (아마도 "머리가 약한 정상 형태"에 압정을 붙일 것입니다.)

첫 번째 근사치로 다음과 같이 지정할 수 있습니다.

  • IO 작업을 실행하면 "필요한"모든식이 평가됩니다. (따라서 IO 액션이 실행되는지 알아야합니다. 예를 들어 이름이 main인지, 아니면 main에서 호출되었는지 그리고 액션에 필요한 것이 무엇인지 알아야합니다.)
  • 평가중인 표현식 (이건 재귀 적 정의입니다!)은 필요한 모든 표현식을 평가합니다.

직관적 인 목록에서 기본 및 IO 작업은 첫 번째 범주에 속하고 시퀀스 및 패턴 일치는 두 번째 범주에 속합니다. 하지만 첫 번째 범주는 "엄격 점"에 대한 당신의 생각과 더 일치한다고 생각합니다. 사실 그것이 우리가 Haskell에서 평가 를 사용자들에게 관찰 할 수 있는 효과 로 만드는 방법이기 때문입니다 .

Haskell은 큰 언어이기 때문에 모든 세부 사항을 구체적으로 제공하는 것은 큰 작업입니다. Concurrent Haskell은 결국 결과를 사용하지 않더라도 추측 적으로 사물을 평가할 수 있기 때문에 매우 미묘합니다. 이것은 평가를 유발하는 세 번째 유형입니다. 두 번째 범주는 상당히 잘 연구 되어 있습니다. 관련된 기능 의 엄격함 을 살펴보고 싶습니다 . 이 때문에 약간의 사기 비록 첫 번째 범주는 너무, "엄격"의 일종으로 생각 될 수 evaluate xseq x $ return ()실제로 다른 것들! IO 모나드에 일종의 의미를 부여하면 적절하게 처리 할 수 ​​있지만 (명시 적으로 RealWorld#토큰을 전달하는 것은 간단한 경우에 작동 함) 일반적으로 이러한 종류의 계층화 된 엄격 성 분석에 대한 이름이 있는지 모르겠습니다.


17

C에는 특정 연산에 대해 한 피연산자가 다른 피연산자보다 먼저 평가된다는 것을 보장하는 시퀀스 포인트 개념 이 있습니다. 나는 이것이 가장 가까운 기존 개념이라고 생각하지만 본질적으로 동등한 용어 인 엄격 점 (또는 아마도 강제 점 )은 하스켈의 사고와 더 일치합니다.

실제로 Haskell은 순전히 게으른 언어가 아닙니다.

프로그래머는 또한 seq 결과가 사용되는지 여부에 관계없이 표현식을 강제로 평가 프리미티브를 .

$! 다음과 같이 정의됩니다. seq .

Lazy vs. Non-strict .

에 대한 당신의 생각 그래서 !/ $!seq본질적으로 잘하지만, 패턴 매칭이 미묘한 규칙이 적용이됩니다. ~물론 지연 패턴 일치를 강제 하는 데 항상 사용할 수 있습니다 . 같은 기사에서 흥미로운 점 :

엄격도 분석기는 또한 외부 표현식에 항상 하위 표현식이 필요한 경우를 찾아서이를 즉시 평가로 변환합니다. 의미론 ( "하단"측면에서)이 변경되지 않기 때문에이를 수행 할 수 있습니다.

토끼 구멍 아래로 계속해서 GHC에서 수행하는 최적화 문서를 살펴 보겠습니다.

엄격도 분석은 GHC가 컴파일 타임에 어떤 데이터가 확실히 '항상 필요'할지 결정하려고 시도하는 프로세스입니다. 그런 다음 GHC는 계산을 저장하고 나중에 실행하기위한 일반 (더 높은 오버 헤드) 프로세스가 아닌 이러한 데이터를 계산하는 코드를 빌드 할 수 있습니다.

GHC 최적화 : 엄격도 분석 .

즉, 엄격한 코드가 생성 될 수 있습니다 어디서나 썽크를 생성하는 데이터가 항상 필요합니다 (및 / 또는 한 번만 사용할 수 있습니다) 때 불필요하게 고가이기 때문에, 최적화 등.

… 값에 대해 더 이상 평가를 수행 할 수 없습니다. 정상적인 형태 라고합니다 . 값에 대해 최소한 몇 가지 평가를 수행하기 위해 중간 단계 중 하나에 있으면 WHNF ( weak head normal form )가됩니다. ( 'head normal form'도 있지만 Haskell에서는 사용되지 않습니다.) WHNF에서 무언가를 완전히 평가하면 정상적인 형태로 축소됩니다.

Wikibooks Haskell : 게으름

( 헤드 위치 1 에 베타 redex가없는 경우 용어는 헤드 정규 형식 입니다. redex는 비 redexes 2 의 람다 추상 자만 앞에 오는 경우 헤드 redex 입니다.) 따라서 썽크를 강제하기 시작하면, 당신은 WHNF에서 일하고 있습니다. 강제 할 썽크가 더 이상 남아 있지 않으면 정상적인 상태입니다. 또 다른 흥미로운 점 :

… 어떤 시점에서 사용자에게 z를 인쇄해야하는 경우이를 완전히 평가해야합니다…

이는 실제로 IO수행되는 모든 작업 main 강제 평가를 수행 한다는 것을 의미합니다. 이는 Haskell 프로그램이 실제로 작업을 수행 한다는 점을 고려할 때 분명해야합니다. 에 정의 된 순서를 거쳐야하는 모든 것은 main정상적인 형식이어야하며 따라서 엄격한 평가를 받아야합니다.

그러나 CA McCann은 의견에서 올바르게 설명했습니다. 특별한 것은 특별 main하다고 main정의 된다는 것뿐입니다 . 생성자에 대한 패턴 일치는 IO모나드 에 의해 부과 된 시퀀스를 보장하기에 충분합니다 . 그 점에서만 seq패턴 매칭이 기본입니다.


4
실제로 "어떤 시점에서 사용자에게 z를 인쇄해야한다면 완전히 평가해야합니다."라는 인용문은 완전히 정확하지 않습니다. Show인쇄되는 값 의 인스턴스 만큼 엄격합니다 .
nominolo

10

Haskell은 AFAIK는 순수한 게으른 언어가 아니라 엄격하지 않은 언어입니다. 즉, 가능한 마지막 순간에 반드시 용어를 평가할 필요는 없습니다.

haskell의 "게으름"모델에 대한 좋은 소스는 여기에서 찾을 수 있습니다. http://en.wikibooks.org/wiki/Haskell/Laziness

기본적으로 썽크와 약한 헤더 일반 형식 WHNF의 차이점을 이해하는 것이 중요합니다.

내 이해는 haskell이 명령형 언어에 비해 계산을 거꾸로 끌어 당긴다는 것입니다. 이것이 의미하는 바는 "seq"및 뱅 패턴이없는 경우 궁극적으로 썽크 평가를 강제하는 일종의 부작용이 될 것이며, 이는 차례로 이전 평가 (진정한 게으름)를 유발할 수 있습니다.

이로 인해 끔찍한 공간 누수가 발생하므로 컴파일러는 공간을 절약하기 위해 미리 썽크를 평가하는 방법과시기를 파악합니다. 그런 다음 프로그래머는 엄격 성 주석 (en.wikibooks.org/wiki/Haskell/Strictness, www.haskell.org/haskellwiki/Performance/Strictness)을 제공하여이 프로세스를 지원하여 중첩 된 썽크 형태로 공간 사용량을 줄일 수 있습니다.

저는 haskell의 작동 의미론에 대한 전문가가 아니므로 링크를 리소스로 남겨 두겠습니다.

더 많은 리소스 :

http://www.haskell.org/haskellwiki/Performance/Laziness

http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation


6

게으른 것은 아무것도하지 않는다는 의미가 아닙니다. 프로그램 패턴이 case표현식 과 일치 할 때마다 어떤 것을 평가합니다. 어쨌든 충분합니다. 그렇지 않으면 사용할 RHS를 파악할 수 없습니다. 코드에 케이스 표현식이 보이지 않습니까? 걱정하지 마십시오. 컴파일러는 코드를 사용을 피하기 어려운 Haskell의 제거 된 형태로 변환합니다.

초보자에게 기본적인 경험 법칙 let은 게으르고 case덜 게으르다는 것입니다.


2
case항상 GHC Core에서 평가를 강제 하지만 일반 Haskell에서는이를 수행하지 않습니다. 예를 들어 case undefined of _ -> 42.
hammar

2
caseGHC Core에서는 WHNF에 대한 인수를 평가하는 반면 caseHaskell 에서는 적절한 분기를 선택하는 데 필요한만큼 인수 평가합니다 . hammar의 예에서는 전혀 그렇지 않지만에서는 case 1:undefined of x:y:z -> 42WHNF보다 더 깊이 평가됩니다.
Max

또한 case something of (y,x) -> (x,y)전혀 평가할 필요가 없습니다 something. 이는 모든 제품 유형에 해당됩니다.
Ingo

@Ingo-틀 렸습니다. something튜플 생성자에 도달하려면 WHNF로 평가되어야합니다.
John L

존-왜? 우리는 그것이 튜플이어야한다는 것을 알고 있습니다. 그래서 그것을 평가하는 요점은 어디입니까? x와 y가 튜플을 평가하고 적절한 슬롯을 추출하는 코드에 바인딩되어 있으면 충분합니다.
Ingo

4

이것은 카르마를 목표로하는 완전한 대답이 아니라 퍼즐의 한 조각 일뿐입니다. 의미론에 관한 것이므로 동일한 의미론 을 제공하는 여러 평가 전략이 있음을 명심하십시오 . 여기에 좋은 예 중 하나가-그리고이 프로젝트는 우리가 일반적으로 하스켈 의미론을 어떻게 생각하는지에 대해 이야기합니다-Eager Haskell 프로젝트로 동일한 의미론 을 유지 하면서 평가 전략을 근본적으로 변경했습니다 : http://csg.csail.mit.edu/ pubs / haskell.html


2

Glasgow Haskell 컴파일러는 코드를 core 라는 Lambda 미적분과 유사한 언어로 변환합니다 . 이 언어에서는-문으로 패턴을 일치시킬 때마다 무언가가 평가 될 것입니다 case. 따라서 함수가 호출되면 가장 바깥 쪽의 생성자와 그 생성자 (강제 필드가없는 경우) 만 평가됩니다. 다른 것은 덩크에 담겨 있습니다. (Thunk는 let바인딩에 의해 도입됩니다 .)

물론 이것은 실제 언어에서 일어나는 일이 아닙니다. 컴파일러는 매우 정교한 방식으로 Haskell을 Core로 변환하여 가능한 한 많은 것을 지연시키고 항상 필요한 모든 것을 지연시킵니다. 또한 항상 엄격한 unboxed 값과 튜플이 있습니다.

손으로 함수를 평가하려고하면 기본적으로 다음과 같이 생각할 수 있습니다.

  • 반환의 가장 바깥 쪽 생성자를 평가 해보십시오.
  • 결과를 얻기 위해 다른 것이 필요한 경우 (정말 필요한 경우에만)도 평가됩니다. 순서는 중요하지 않습니다.
  • IO의 경우 처음부터 마지막까지 모든 명령문의 결과를 평가해야합니다. IO 모나드는 특정 순서로 평가를 강제하기 위해 몇 가지 트릭을 수행하기 때문에 이것은 조금 더 복잡합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.