확장 MonoLocalBinds (스케치 중)

Posted on November 19, 2024

GHC User Guide 6.12.2. Let-generalisation

로컬 다형성Local Polymorphic (※ 로컬은 문법적으로 탑레벨이 아닌 걸 말합니다.)
let이나 where로 정의한 로컬(let-bound 또는 where-bound) 함수의 다형적 동작에 관한 확장입니다.

다형으로 추론 할까? 단일 형으로 추론할까?

설명보다 코드를 보는 게 이해가 더 빠릅니다. (Let generalisation in GHC 7.0에서 코드 발췌)

{-# LANGUAGE MonoLocalBinds #-}
f x = (g 'v', g True)
    where
      g y = (x,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'를 만나면서, gChar를 받는 함수로 추론했는데, 두 번째 g를 실행할 때 Bool이 들어와 문제입니다. g가 일반화되지 않았다고 말합니다. g가 다형적이지 않습니다. 확장을 따로 써주지 않으면 (TypeFamiliesGADTs 확장을 쓰면, 암시적으로 MonoLocalBinds로 켜지는데, 이 때는 NoMonoLocalBinds 확장으로 끌 수 있습니다.) 늘 봐왔던 대로 다형으로 추론하고 정상 컴파일됩니다. 이름 그대로 Local BindsMono, 즉 한가지 타입으로 추론하겠다는 확장인데, 왜 이런 확장이 필요할까요?

ML 스타일 언어들은 보통 let 바인딩이나 where 바인딩 변수들을 일반화합니다. 다형적이란 뜻입니다. MonoLocalBinds를 켜면 GHC는 좀 더 보수적인 판단을 한다는데, 보수적이란 게 빡빡하게 제약을 두겠다는 뜻이겠지요? 다형성을 허용할 수 있는 상황에서도 최대한 모노모픽, 즉 단일 타입으로 추론하겠다는 말입니다.

단일 형으로 강제 추론이 왜 필요할까?

MonoLocalBinds일 때 탑 레벨 바인딩은 바뀌는 것 없이 다형적으로 추론되고, 로컬 바인딩은 단일 타입으로 추론됩니다. 탑레벨이면 타입 환경에 free 타입 변수가 없기 때문에 논문에서 말하는 어려움이 생기지 않습니다. 그러나, 프로그래머는 스타일상의 이유로 때때로 로컬 변수를 쓰지 않는, 탑 레벨 바인딩 같은 로컬 바인딩을 작성합니다. 이럴 때는 일반화 하는게 더 합당해 보입니다.

MonoLocalBinds를 켜면 로컬 바인딩을 단일 타입 추론하겠다는 얘기지만, 확장을 켜도 정확히 다음과 같은 상황에선 바인딩 그룹이 일반화 됩니다. - 탑 레벨 바인딩 그룹이거나 - 자유free 변수 각 각이 닫혀있거나closed - 바인더(로컬에서 정의 중인 함수)들 중 부분 타입 서명을 가지고 있는 게 있을 때. MonoLocalBinds가 켜져 있더라도, 부분 타입 서명 f :: _ 추가하면(또는 더 일반적으로 f :: _ => _) 바인딩 별로 GHC에게 let 일반화를 요청합니다.

바인딩이 일반화가 되어도, 모든 자유 타입 변수에 대해 일반화가 된 건 아닐 수 있습니다.

특정 상황에서 반대되는 동작이 필요할 때가 있는데, 그 때는 NoMonomorphismRestriction을 이용합니다.

g v = ...
  where
    f1 x = x + 1
    f2 y = f1 (y * 2)

f1은 자유 변수를 가지고 있습니다.

f3 x = let g y = x + y in ...

x 자유 변수는 람다-바인딩 되어 있으니, g는 닫혀있지 않습니다. 그래서, g의 바인딩은 일반화 되지 않습니다.

탑레벨 바인딩. MonoMorphism 제한은 탑레벨 바인딩이어도 일반화하지 않을 수도 있습니다. 그래서, 탑레벨 환경에서도 free 타입 변수를 가질 수 있지만, 그럼에도 탑레벨 바인딩은 일반화되지 않습니다.

module M( f ) where
  x = 5
  f v = (v, x)

x=5 바인딩은 MonoMorphism Restriction 아래에 놓여 일반화되지 않습니다. 그래서 f의 바인딩은 닫혀 있지 않은 상태가 됩니다.

좀 더 복잡한 규칙을 정했습니다.

{-# LANGUAGE MonoLocalBinds #-}
f x = (k 'v', k True)
    where
      h y = (y,y)  -- Note: x가 없습니다.
      k z = (h z, h 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))
f x = (g 'v', g True)
    where
      g :: forall b. b -> (a,b)
      g y = (x,y)

별 쓸모는 없지만, 설명을 위해 a를 받으면, ((a,'v'), (a, True)) 튜플을 반환하는 함수입니다. g함수는 'v'글자와 True에 적용하는 다형 함수입니다. g 서명에 있는 a는, g 함수내에서 x가 쓰이고 있기 때문에, 모든 타입이 아니라 x의 타입이어야 합니다. 하지만, ga에는 모노모픽합니다.

GHC 6.12에선 타입 서명을 써주지 않아도 g의 다형 타입을 추론해서 컴파일 되지만, GADTsTypeFamilies를 쓰면 컴파일이 되지 않습니다. 혹은 GHC 7.0을 쓰면 컴파일되지 않습니다.

-XMonoLocalBinds 확장을 쓰면 탑 레벨이 아니고 로컬인 let where 바인딩은 일반화generalised되지 않습니다.


  1. Polymorphic 다형, MonoMorphic 단일 형으로 번역어를 잡으니, 어색한 곳이 나오긴 하는데, 일단 넘어가겠습니다.↩︎

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com