GHC User Guide 6.12.2. Let-generalisation
로컬 다형성Local Polymorphic (※ 로컬은 문법적으로 탑레벨이 아닌 걸 말합니다.)
let
이나 where
로 정의한 로컬(let-bound 또는 where-bound) 함수의 다형적 동작에 관한 확장입니다.
설명보다 코드를 보는 게 이해가 더 빠릅니다. (Let generalisation in GHC 7.0에서 코드 발췌)
{-# LANGUAGE MonoLocalBinds #-}
= (g 'v', g True)
f x where
= (x,y) g y
g
함수가 받는 y
가 다형적1이면 될 것 같은데, 컴파일하면 다음과 같은 에러가 납니다.
Couldn't match expected type `Char' with actual type `Bool'
In the first argument of `g', namely `True'
In the expression: g True
In the expression: (g 'v', g True)
g 'v'
를 만나면서, g
를 Char
를 받는 함수로 추론했는데, 두 번째 g
를 실행할 때 Bool
이 들어와 문제입니다. g
가 일반화되지 않았다고 말합니다. g
가 다형적이지 않습니다. 확장을 따로 써주지 않으면 (TypeFamilies
나 GADTs
확장을 쓰면, 암시적으로 MonoLocalBinds
로 켜지는데, 이 때는 NoMonoLocalBinds
확장으로 끌 수 있습니다.) 늘 봐왔던 대로 다형으로 추론하고 정상 컴파일됩니다. 이름 그대로 Local
Binds
를 Mono
, 즉 한가지 타입으로 추론하겠다는 확장인데, 왜 이런 확장이 필요할까요?
ML 스타일 언어들은 보통 let
바인딩이나 where
바인딩 변수들을 일반화합니다. 다형적이란 뜻입니다. MonoLocalBinds
를 켜면 GHC는 좀 더 보수적인 판단을 한다는데, 보수적이란 게 빡빡하게 제약을 두겠다는 뜻이겠지요? 다형성을 허용할 수 있는 상황에서도 최대한 모노모픽, 즉 단일 타입으로 추론하겠다는 말입니다.
MonoLocalBinds
일 때 탑 레벨 바인딩은 바뀌는 것 없이 다형적으로 추론되고, 로컬 바인딩은 단일 타입으로 추론됩니다. 탑레벨이면 타입 환경에 free 타입 변수가 없기 때문에 논문에서 말하는 어려움이 생기지 않습니다. 그러나, 프로그래머는 스타일상의 이유로 때때로 로컬 변수를 쓰지 않는, 탑 레벨 바인딩 같은 로컬 바인딩을 작성합니다. 이럴 때는 일반화 하는게 더 합당해 보입니다.
MonoLocalBinds
를 켜면 로컬 바인딩을 단일 타입 추론하겠다는 얘기지만, 확장을 켜도 정확히 다음과 같은 상황에선 바인딩 그룹이 일반화 됩니다.
- 탑 레벨 바인딩 그룹이거나
- 자유free 변수 각 각이 닫혀있거나closed
- 바인더(로컬에서 정의 중인 함수)들 중 부분 타입 서명을 가지고 있는 게 있을 때. MonoLocalBinds
가 켜져 있더라도, 부분 타입 서명 f :: _
추가하면(또는 더 일반적으로 f :: _ => _
) 바인딩 별로 GHC에게 let
일반화를 요청합니다.
바인딩이 일반화가 되어도, 모든 자유 타입 변수에 대해 일반화가 된 건 아닐 수 있습니다.
특정 상황에서 반대되는 동작이 필요할 때가 있는데, 그 때는 NoMonomorphismRestriction
을 이용합니다.
= ...
g v where
= x + 1
f1 x = f1 (y * 2) f2 y
f1
은 자유 변수를 가지고 있습니다.
= let g y = x + y in ... f3 x
x
자유 변수는 람다-바인딩 되어 있으니, g
는 닫혀있지 않습니다. 그래서, g
의 바인딩은 일반화 되지 않습니다.
탑레벨 바인딩. MonoMorphism 제한은 탑레벨 바인딩이어도 일반화하지 않을 수도 있습니다. 그래서, 탑레벨 환경에서도 free 타입 변수를 가질 수 있지만, 그럼에도 탑레벨 바인딩은 일반화되지 않습니다.
module M( f ) where
= 5
x = (v, x) f v
x=5
바인딩은 MonoMorphism Restriction 아래에 놓여 일반화되지 않습니다. 그래서 f
의 바인딩은 닫혀 있지 않은 상태가 됩니다.
좀 더 복잡한 규칙을 정했습니다.
{-# LANGUAGE MonoLocalBinds #-}
= (k 'v', k True)
f x where
= (y,y) -- Note: x가 없습니다.
h y = (h z, h z) k z
With -XMonoLocalBinds (the default), a binding without a type signature is generalised only if all its free variables are closed.
가이드에 있는 설명
{-# LANGUAGE ScopedTypeVariables #-}
f :: forall a. a -> ((a, Char), (a, Bool))
= (g 'v', g True)
f x where
g :: forall b. b -> (a,b)
= (x,y) g y
별 쓸모는 없지만, 설명을 위해 a
를 받으면, ((a,'v'), (a, True))
튜플을 반환하는 함수입니다. g
함수는 'v'
글자와 True
에 적용하는 다형 함수입니다. g
서명에 있는 a
는, g
함수내에서 x
가 쓰이고 있기 때문에, 모든 타입이 아니라 x의 타입
이어야 합니다. 하지만, g
는 a
에는 모노모픽합니다.
GHC 6.12에선 타입 서명을 써주지 않아도 g
의 다형 타입을 추론해서 컴파일 되지만, GADTs
나 TypeFamilies
를 쓰면 컴파일이 되지 않습니다. 혹은 GHC 7.0을 쓰면 컴파일되지 않습니다.
-XMonoLocalBinds
확장을 쓰면 탑 레벨이 아니고 로컬인 let
where
바인딩은 일반화generalised되지 않습니다.
Polymorphic 다형, MonoMorphic 단일 형으로 번역어를 잡으니, 어색한 곳이 나오긴 하는데, 일단 넘어가겠습니다.↩︎