참조 투명성은 무엇입니까?


38

나는 명령형 패러다임에서

f (x) + f (x)

다음과 같지 않을 수 있습니다.

2 * f (x)

그러나 기능적 패러다임에서는 동일해야합니다. 파이썬과 Scheme 에서 두 가지 경우를 모두 구현하려고 시도했지만 나에게는 똑같이 보입니다.

주어진 함수와의 차이점을 지적 할 수있는 예는 무엇입니까?


7
파이썬에서 참조 적으로 투명한 함수를 작성할 수 있으며 종종 할 수도 있습니다. 차이점은 언어가 그것을 강요하지 않는다는 것입니다.
Karl Bielefeldt

5
C와 비슷하게 : f(x++)+f(x++)동일하지 않을 수도 있습니다 . 2*f(x++)(C에서는 매크로 안에 숨겨져있을 때 특히 사랑 스럽습니다. 코를 부러
뜨렸

내 이해에서 @gnat의 예는 R과 같은 기능 지향 언어가 통과 기준을 사용하고 인수를 수정하는 함수를 명시 적으로 피하는 이유입니다. R에서는 적어도 언어의 복잡한 환경 시스템과 네임 스페이스 및 검색 경로를 파고 들지 않고 이러한 제한을 (적어도 안정적이고 이식 가능한 방식으로) 적용하는 것이 실제로 어려울 수 있습니다.
shadowtalker

4
@ssdecontrol : 실제로, 참조 투명성을 가질 때, 값별 및 참조 별은 항상 정확히 동일한 결과를 산출하므로 언어가 어느 것을 사용하는지는 중요하지 않습니다. 기능적 언어는 의미 상 명확성을 위해 값을 전달하는 것과 유사한 것으로 자주 지정되지만, 구현시 성능을 위해 참조를 통한 전달을 사용합니다 (또는 주어진 컨텍스트에 대해 어느 것이 더 빠른지에 따라 둘 다).
Jörg W Mittag

4
@ gnat : 특히 f(x++)+f(x++)정의되지 않은 동작을 호출하기 때문에 절대적으로 무엇이든 될 수 있습니다. 그러나 그것은 실제로 참조 투명성과 관련이 없습니다.이 호출에는 도움이되지 않지만 참조 투명 기능에 대해서도 '정의되지 않음'과 같습니다 sin(x++)+sin(x++). ... 악마는 사용자의 코를 밖으로 비행 할 수, 42 수 있을까 하드 드라이브를 포맷 할 수
크리스토퍼 Creutzig

답변:


62

함수라고하는 참조 투명도는 인수 값을보고 만 해당 함수를 적용한 결과를 결정할 수 있음을 나타냅니다. Python, Scheme, Pascal, C와 같은 프로그래밍 언어로 참조 용으로 투명한 함수를 작성할 수 있습니다.

반면에 대부분의 언어에서는 참조가 아닌 투명한 함수를 작성할 수도 있습니다. 예를 들어,이 Python 함수는

counter = 0

def foo(x):
  global counter

  counter += 1
  return x + counter

실제로 전화하는 것은 투명하지 않습니다.

foo(x) + foo(x)

2 * foo(x)

인수에 대해 다른 값을 생성합니다 x. 그 이유는 함수가 전역 변수를 사용하고 수정하기 때문에 각 호출의 결과는 함수의 인수뿐만 아니라이 변경 상태에 따라 달라지기 때문입니다.

하스켈, 순수 함수형 언어가 엄격히 분리 식 평가 하는 순수한 기능 인가로부터 항상 referentially 투명되는 동작 실행 마다 A를 가질 수있는 동일한 기능을 실행 referentially 투명하지 않다 (특별 값들의 처리), 즉 다른 결과.

어떤 하스켈 함수 든

f :: Int -> Int

그리고 모든 정수 x는 항상

2 * (f x) == (f x) + (f x)

조치의 예는 라이브러리 함수의 결과입니다 getLine.

getLine :: IO String

식 평가의 결과로이 함수 (실제로 상수)는 우선 순수한 type 값을 생성합니다 IO String. 이 유형의 값은 다른 값과 같습니다. 값을 전달하고, 데이터 구조에 넣고, 특수 함수를 사용하여 작성하는 등의 작업을 수행 할 수 있습니다. 예를 들어 다음과 같은 작업 목록을 만들 수 있습니다.

[getLine, getLine] :: [IO String]

작업은 다음과 같이 작성하여 Haskell 런타임에 실행하도록 지시 할 수 있다는 점에서 특별합니다.

main = <some action>

이 경우, 하스켈 프로그램이 시작될 때, 런타임에 바인딩 작업을 통해 안내 main하고 실행 가능한 부작용을 생산하고, 그것을. 따라서 동일한 조치를 두 번 실행하면 런타임이 입력으로 가져 오는 내용에 따라 다른 결과가 생성 될 수 있으므로 조치 실행은 참조 적으로 투명하지 않습니다.

Haskell의 유형 시스템 덕분에 다른 유형이 예상되는 상황에서는 작업을 사용할 수 없으며 그 반대도 마찬가지입니다. 따라서 문자열의 길이를 찾으려면 length함수를 사용할 수 있습니다 .

length "Hello"

터미널에서 읽은 문자열의 길이를 찾으려면 쓸 수 없습니다.

length (getLine)

유형 오류가 발생하기 때문에 : length유형 목록의 입력 (및 문자열은 실제로 목록)을 예상하지만 getLine유형의 값 IO String(조치)입니다. 이런 식으로, 타입 시스템은 getLine(핵심 언어 외부에서 실행되고 비참 조적으로 투명 할 수있는) 같은 액션 값이 타입의 액션이 아닌 값 안에 숨겨 질 수 없도록합니다 Int.

편집하다

exizt 질문에 대답하기 위해 콘솔에서 줄을 읽고 길이를 인쇄하는 작은 Haskell 프로그램이 있습니다.

main :: IO () -- The main program is an action of type IO ()
main = do
          line <- getLine
          putStrLn (show (length line))

기본 조치는 순차적으로 실행되는 두 개의 하위 조치로 구성됩니다.

  1. getline유형 IO String
  2. 두 번째는 인수에 대한 putStrLn유형 의 함수 를 평가하여 구성됩니다 String -> IO ().

보다 정확하게는 두 번째 동작은

  1. line첫 번째 작업에서 읽은 값에 바인딩
  2. 순수 함수를 length계산하고 (길이를 정수로 계산) show정수를 문자열로 변환
  3. 함수를 적용함으로써 작업 구축 putStrLn의 결과 show.

이 시점에서 두 번째 작업을 실행할 수 있습니다. "Hello"를 입력 한 경우 "5"가 인쇄됩니다.

<-표기법을 사용하여 조치에서 값을 얻는 경우 다른 조치 내에서만 해당 값을 사용할 수 있습니다 (예 : 쓸 수 없음).

main = do
          line <- getLine
          show (length line) -- Error:
                             -- Expected type: IO ()
                             --   Actual type: String

show (length line)유형이 있기 때문에 Stringdo 표기법은 조치 ( getLine유형 IO String) 뒤에 다른 조치 (예 : putStrLn (show (length line))유형 IO ())가 필요합니다.

편집 2

Jörg W Mittag의 참조 투명성에 대한 정의는 내 것보다 더 일반적입니다 (저는 그의 대답을 상향 조정했습니다). 질문의 예가 ​​함수의 반환 값에 중점을 두고이 측면을 설명하고 싶기 때문에 제한된 정의를 사용했습니다. 그러나 일반적으로 RT는 전역 상태 변경 및 식 평가로 인한 환경 (IO)과의 상호 작용을 포함하여 전체 프로그램의 의미를 나타냅니다. 따라서 올바른 일반 정의를 위해서는 해당 답변을 참조해야합니다.


10
downvoter가이 답변을 개선 할 수있는 방법을 제안 할 수 있습니까?
Giorgio

그렇다면 Haskell의 터미널에서 읽은 문자열의 길이를 어떻게 얻을 수 있습니까?
sbichenko

2
이것은 극도로 비현실적이지만 완전성을 위해 행동과 순수한 기능이 섞이지 않도록하는 Haskell의 유형 시스템은 아닙니다. 그것은 언어가 직접 호출 할 수있는 불순한 기능을 제공하지 않는다는 사실입니다. IO람다와 제네릭을 사용하여 어떤 언어로든 하스켈 타입을 실제로 쉽게 println구현할 수 있지만 누구나 직접 호출 할 수 있기 때문에 구현 IO이 순도를 보장하지는 않습니다. 그것은 단지 컨벤션 일뿐입니다.
Doval

나는 (1) 모든 기능이 순수하다는 것을 의미했다. 불순한 행동에는 유형이 다르므로 혼합 할 수 없습니다. BTW, 직접 전화한다는 것은 무슨 뜻 입니까?
Giorgio

6
getLine참조가 투명하지 않다는 요점 이 잘못되었습니다. 당신은 getLine그것이 어떤 문자열로 평가되거나 줄어드는 것처럼 제시 하고 있는데, 그 특정한 문자열은 사용자의 입력에 달려 있습니다. 이것은 올바르지 않습니다. IO String더 이상 문자열을 포함하지 않습니다 Maybe String. IO String아마도 문자열을 얻을 수있는 방법이며, 식으로 Haskell의 다른 것만 큼 순수합니다.
LuxuryMode

25
def f(x): return x()

from random import random
f(random) + f(random) == 2*f(random)
# => False

그러나 이것이 참조 투명도의 의미는 아닙니다 . 당신이 대체 할 수 있음을 RT 수단 어떤 프로그램의 의미를 변경하지 않고 그 표현 (또는 그 반대)를 평가 한 결과로 프로그램의 표현.

예를 들어 다음 프로그램을 보자.

def f(): return 2

print(f() + f())
print(2)

이 프로그램은 참조가 투명합니다. 나는 하나 또는 모두 발행 수 대체 할 수있는 f()과를 2하고 그것은 여전히 동일하게 작동합니다 :

def f(): return 2

print(2 + f())
print(2)

또는

def f(): return 2

print(f() + 2)
print(2)

또는

def f(): return 2

print(2 + 2)
print(f())

모두 동일하게 작동합니다.

글쎄, 실제로, 나는 속였다. print프로그램의 의미를 변경하지 않고 호출을 반환 값 (아무 값도 아님) 으로 바꿀 수 있어야합니다 . 그러나 분명히 두 print문장을 제거하면 프로그램의 의미가 바뀔 것입니다. I / O는 참조가 투명하지 않습니다.

간단한 경험 법칙은 다음과 같습니다. 프로그램의 의미를 변경하지 않고 표현식, 하위 표현식 또는 서브 루틴 호출을 프로그램의 어느 곳에서나 해당 표현식, 하위 표현식 또는 서브 루틴 호출의 리턴 값으로 대체 할 수있는 경우 참조가 있습니다. 투명도. 그리고 이것이 실제로 의미하는 것은 실제로 I / O를 가질 수없고, 변경 가능한 상태를 가질 수 없으며, 부작용을 가질 수 없다는 것입니다. 모든 표현에서, 표현의 가치는 표현의 구성 부분의 가치에만 의존해야한다. 그리고 모든 서브 루틴 호출에서 반환 값은 인수에만 의존해야합니다.


4
"변경 가능한 상태를 가질 수 없음": 숨겨져 있고 관찰 가능한 코드 동작에 영향을 미치지 않으면 가질 수 있습니다. 예를 들어 메모에 대해 생각하십시오.
조르지오

4
@Giorgio : 이것은 아마도 주관적이지만, 캐시 된 결과가 숨겨져 있고 관찰 가능한 효과가 없다면 실제로 "변경 가능한 상태"가 아니라고 주장합니다. 불변성은 항상 가변 하드웨어 위에 구현 된 추상화입니다. 종종 언어에 의해 제공됩니다 (실행 중에 레지스터와 메모리 위치 사이에서 값이 이동할 수 있고 다시 사용되지 않을 경우 사라질 수 있음에도 "값"의 추상화를 제공함). 도서관에서 제공하거나 기타 (물론 올바르게 구현되었다고 가정)
ruakh

1
+1 저는 print예제를 정말 좋아합니다 . 아마도 이것을 보는 한 가지 방법은 화면에 인쇄되는 것이 "반환 값"의 일부라는 것입니다. print함수 반환 값과 터미널에서 동등한 쓰기로 바꿀 수 있으면 예제가 작동합니다.
Pierre Arlaud

1
@Giorgio 공간 / 시간 사용은 참조 투명성을 위해 부작용으로 간주 될 수 없습니다. 즉 만들 것 42 + 2비 상호 교환이 서로 다른 실행 시간이 있기 때문에, 및 참조 투명성의 요점은 당신이에 평가 무엇 이건 식을 대체 할 수 있다는 것입니다. 중요한 고려 사항은 나사산 안전입니다.
Doval

1
@overexchange : 참조 투명도는 프로그램의 의미를 변경하지 않고 모든 하위 표현식을 해당 값으로 바꿀 수 있음을 의미합니다. listOfSequence.append(n)반환 None, 그래서 당신은 모든 호출을 대체 할 수있을 것 listOfSequence.append(n)None프로그램의 의미를 변경하지 않고. 그렇게 할 수 있습니까? 그렇지 않은 경우, 참조 적으로 투명하지 않습니다.
Jörg W Mittag

1

이 답변의 일부는 내 GitHub 계정에서 호스팅되는 기능적 프로그래밍에 대한 미완성 튜토리얼에서 직접 가져옵니다 .

함수는 동일한 입력 매개 변수가 주어지면 항상 동일한 출력 (반환 값)을 생성하는 경우 참조 적으로 투명하다고합니다. 순수한 함수형 프로그래밍을위한 raison d' être를 찾고 있다면 참조 투명성이 좋은 후보입니다. 대수, 산술 및 논리의 수식으로 추론 할 때이 속성 (등가의 동등성이라고도 함)은 근본적으로 중요하므로 당연한 것으로 간주됩니다.

간단한 예를 생각해보십시오.

x = 42

순수한 기능적 언어에서 등호의 왼쪽과 오른쪽은 두 가지 방법으로 서로 대체 가능합니다. 즉, C와 같은 언어와 달리 위의 표기법은 진정으로 평등을 주장합니다. 그 결과 수학 방정식과 마찬가지로 프로그램 코드를 추론 할 수 있습니다.

에서 하스켈 위키 :

순수한 계산은 호출 될 때마다 동일한 값을 산출합니다. 이 속성을 참조 투명성 이라고 하며 코드에서 방정식 추론 을 수행 할 수 있습니다.

이와는 대조적으로, C와 유사한 언어에 의해 수행되는 작업 유형은 종종 파괴적 할당 이라고 합니다 .

순수한 용어 는 종종이 논의와 관련된 표현의 속성을 설명하는 데 사용됩니다. 함수가 순수한 것으로 간주 되려면

  • 부작용을 보일 수 없으며
  • 참조 용으로 투명해야합니다.

수많은 수학 교과서에서 볼 수있는 블랙 박스 비유에 따르면 함수의 내부는 외부 세계와 완전히 분리되어 있습니다. 부작용은 함수 나 표현이이 원칙을 위반할 때입니다. 즉, 절차는 다른 프로그램 장치와 어떤 방식 으로든 통신 할 수 있습니다 (예 : 정보 공유 및 교환).

요약하면, 참조 투명성은 함수가 프로그래밍 언어의 시맨틱에서도 true , 수학 함수 처럼 동작하기 위해 반드시 필요한 요소 입니다.


이 열립니다 보인다 단어 단위 여기에서 촬영 복사 스택 Exchange가있다 "함수는이 같은 입력 매개 변수 주어, 항상 동일한 출력 ... 생산하는 경우 referentially 투명하게 말한다" 표절에 대한 규칙을 하며, 이것에 대해 알고 있습니까? "표절은 다른 사람의 작품 덩어리를 복사하여 그 이름을 때리고 원래 작가로 자신을 전달하는 영혼없는 행동입니다 ..."
gnat

3
나는 그 페이지를 썼다.
yesthisisuser

이 경우 독자가 말할 수있는 방법이 없기 때문에 표절을 줄 이도록하십시오. SE에서이 작업을 수행하는 방법을 알고 있습니까? 1) 당신은 "나는 (나) 썼다 [here](link to source)..."와 같이 2) 적절한 인용 형식 (따옴표를 사용하거나 그보다 더 나은 > 기호 )과 같은 원본 소스를 참조하십시오 . 일반적인 지침을 제공하는 것 외에도이 질문에 대한 구체적인 질문에 답변하고,이 경우 f(x)+f(x)/ 2*f(x)에 대한 답변 방법을 참조하십시오. 그렇지 않으면 단순히 페이지를 광고하는 것처럼 보일 수 있습니다.
gnat

1
이론적으로이 답을 이해했습니다. 그러나 실제로 이러한 규칙을 따르면이 프로그램 에서 우박 시퀀스 목록을 반환해야합니다 . 어떻게해야합니까?
overexchange
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.