함수형 프로그래밍에서 부작용이없는 지역 가변 변수가 여전히 "나쁜 습관"으로 간주됩니까?


23

내부에서만 사용되는 함수에 가변 로컬 변수가 있습니까 (예 : 함수에 의도적으로 부작용이 없음) 여전히 "비 기능"으로 간주됩니까?

예를 들어 "스칼라 기능 프로그래밍"코스 스타일 검사에서 var사용이 잘못된 것으로 간주합니다

함수에 부작용이없는 경우 제 질문은 명령형 스타일 코드 작성이 여전히 권장되지 않습니까?

예를 들어 누산기 패턴에 꼬리 재귀를 사용하는 대신 입력을 변경하지 않는 한 로컬 for 루프를 수행하고 로컬 변경 가능 변수를 ListBuffer만들고 추가 하는 데 무엇이 문제가 있습니까?

대답이 "예, 부작용이 없더라도 항상 권장하지 않습니다"라면 그 이유는 무엇입니까?


3
내가 들어 본 주제에 대한 모든 조언, 권고 등은 공유 가능한 변경 가능 상태를 복잡성의 원천이라고합니다. 이 과정은 초보자 만 이용할 수 있습니까? 그런 다음 의도적으로 의도적으로 지나치게 단순화 한 것일 수 있습니다.
Kilian Foth

3
@KilianFoth : 공유 가변 상태는 다중 스레드 컨텍스트에서 문제이지만 공유 불가능 가변 상태는 프로그램의 추론을 어렵게 만들 수 있습니다.
Michael Shaw

1
로컬 가변 변수를 사용하는 것이 반드시 나쁜 습관은 아니지만 "기능적 스타일"은 아니라고 생각합니다. 스칼라 코스의 목적은 (작년 가을에했던) 기능적 스타일로 프로그래밍하는 것을 가르치는 것입니다. 기능적 스타일과 명령형 스타일을 명확하게 구별 할 수있게되면 언제 사용할 것인지 결정할 수 있습니다 (프로그래밍 언어가 둘 다 허용하는 경우). var항상 작동하지 않습니다. 스칼라는 게으른 발과 꼬리 재귀 최적화를 통해 바를 완전히 피할 수 있습니다.
Giorgio

답변:


17

여기서 명백히 나쁜 습관 중 하나 는 그렇지 않은 경우 무언가가 순수한 기능 이라고 주장 하는 것입니다.

변하기 쉬운 변수가 진정으로 완전히 포함 된 방식으로 사용된다면, 함수는 외부 적으로 순수하며 모든 사람들이 행복합니다. Haskell은 실제로 이것을 명시 적으로 지원 하며 유형 시스템을 통해 참조를 생성하는 함수 외부에서 변경 가능한 참조를 사용할 수 없습니다.

즉, "부작용"에 대해 말하는 것이 그것을 보는 가장 좋은 방법은 아니라고 생각합니다 (그리고 위의 "순수"라고 말한 이유도 있습니다). 함수와 외부 상태 사이의 종속성을 만드는 것은 무엇이든 추론하기 어렵게 만들고 현재 시간을 알거나 스레드가 안전한 방식으로 숨겨진 가변 상태를 사용하는 것과 같은 것을 포함합니다.


16

문제는 그 자체로 가변성이 아니며 참조 투명성이 부족하다는 것입니다.

참조 적으로 투명한 사물과 이에 대한 참조는 항상 동일해야하므로 참조 적으로 투명한 함수는 주어진 입력 세트에 대해 항상 동일한 결과를 반환하며 참조 적으로 투명한 "변수"는 실제로 변수가 아닌 값이므로 변경할 수 없습니다. 내부에 가변 변수가있는 투명한 참조 함수를 만들 수 있습니다. 그건 문제가되지 않습니다. 그러나 수행중인 작업에 따라 기능이 참조 투명성을 보장하는 것이 더 어려울 수 있습니다.

매우 기능적인 작업을 수행하기 위해 변경 가능성을 사용해야하는 곳이 하나 있습니다. 메모 화는 함수에서 값을 캐싱하므로 다시 계산할 필요가 없습니다. 참조 적으로 투명하지만 돌연변이를 사용합니다.

그러나 일반적으로 참조 투명 함수 및 메모의 로컬 변경 가능 변수 이외의 참조 투명성과 불변성은 함께 사용됩니다. 그렇지 않은 다른 예가 있는지 확실하지 않습니다.


4
메모에 대한 당신의 요점은 매우 좋습니다. Haskell은 프로그래밍을위한 참조 투명성을 강조하지만, 게으른 평가의 메모 화와 유사한 동작은 뒤에서 언어 런타임에 의해 수행되는 엄청난 양의 돌연변이를 포함합니다.
CA McCann

@CA McCann : 나는 당신이하는 말이 매우 중요하다고 생각합니다. 함수형 언어에서는 런타임이 돌연변이를 사용하여 계산을 최적화 할 수 있지만, 언어에는 프로그래머가 돌연변이를 사용할 수있는 구문이 없습니다. 또 다른 예는 루프 변수가있는 while 루프입니다. Haskell에서는 가변 변수로 구현할 수있는 꼬리 재귀 함수를 작성할 수 있지만 (스택 사용을 피하기 위해) 프로그래머가 보는 것은 하나에서 전달되는 불변 함수 인수입니다. 다음으로 전화하십시오.
Giorgio

@Michael Shaw : "문제 자체는 변경이 아니며 참조 투명성이 부족합니다."+1 고유 유형이있는 Clean 언어를 인용 할 수도 있습니다. 이들은 변경을 허용하지만 참조 투명성을 보장합니다.
조르지오

@Giorgio : Clean에 대해 전혀 알지 못하지만 가끔 언급 한 것을 들었습니다. 어쩌면 내가 조사해야 할 수도 있습니다.
Michael Shaw

@Michael Shaw : Clean에 대해 잘 모르지만 참조 투명성을 보장하기 위해 고유 유형을 사용한다는 것을 알고 있습니다. 기본적으로 수정 후 이전 값에 대한 참조가없는 경우 데이터 개체를 수정할 수 있습니다. IMO는 이것이 당신의 요점을 설명합니다 : 참조 투명성이 가장 중요한 요점이며 불변성은 그것을 보장하는 한 가지 방법 일뿐입니다.
Giorgio

8

이것을 "좋은 연습"대 "나쁜 연습"으로 요약하면 좋지 않습니다. 스칼라는 불변 값보다 특정 문제, 즉 본질적으로 반복되는 문제보다 훨씬 나은 문제를 해결하기 때문에 변경 가능한 값을 지원합니다.

관점에서 볼 때, CanBuildFromscala가 제공하는 거의 모든 불변 구조 를 통해 내부적으로 어떤 종류의 돌연변이가 발생 한다고 확신합니다 . 요점은 그들이 노출하는 것이 불변이라는 것입니다. 가능한 많은 값을 불변으로 유지하면 프로그램을 보다 쉽게 ​​추론 하고 오류가 발생하기 쉽습니다 .

이것은 가변성에 더 적합한 문제가있을 때 내부에서 가변 구조와 값을 피해야한다는 것을 의미하지는 않습니다.

이를 염두에두고, 스칼라와 같은 언어 (map / filter / fold)와 같은 언어가 제공하는 고차 함수를 사용하면 일반적으로 가변 변수 (예 : 루핑)가 필요한 많은 문제를 더 잘 해결할 수 있습니다. 그것들을 알고 있어야합니다.


2
그러나 Scala의 컬렉션을 사용할 때 for 루프가 거의 필요하지 않습니다. map, filter, foldLeftforEach 대부분의 시간 트릭을 할,하지만 그들이하지 않을 때, 나는 짐승 힘 필수 코드로 되돌아에 "OK"를 해요 느낄 수있는 것은 좋은입니다. (물론 부작용이없는 한)
Eran Medan

3

스레드 안전과 관련된 잠재적 인 문제 외에도 일반적으로 많은 유형 안전이 손실됩니다. 명령형 루프는 리턴 유형을 Unit가지며 입력에 대해 거의 모든 표현식을 취할 수 있습니다. 고차 함수와 재귀는 훨씬 더 정확한 의미와 유형을 갖습니다.

또한 명령형 루프보다 기능 컨테이너 처리에 더 많은 옵션이 있습니다. 필수적으로, 당신은 기본적으로 가지고 for, while그리고 그와 같은 두 가지에 작은 변화 do...whileforeach.

기능적으로 집계, 개수, 필터링, 찾기, flatMap, 폴드, groupBy, lastIndexWhere, map, maxBy, minBy, 파티션, 스캔, sortBy, sortWith, span 및 takeWhile이 있습니다. 표준 라이브러리. 사용 가능한 것에 익숙해지면 명령 for루프가 너무 기본적으로 보입니다.

로컬 변경 가능성을 사용하는 유일한 실제 이유는 성능을 위해 매우 가끔입니다.


2

나는 그것이 대부분 괜찮다고 말할 것입니다. 더구나, 이런 식으로 구조를 생성하는 것은 경우에 따라 성능을 향상시키는 좋은 방법 일 수 있습니다. Clojure는 과도 데이터 구조 를 제공하여이 문제를 해결했습니다 .

기본 아이디어는 제한된 범위의 국소 돌연변이를 허용 한 다음 구조를 반환하기 전에 동결시키는 것입니다. 이런 식으로 사용자는 코드가 순수한 것처럼 여전히 추론 할 수 있지만 필요할 때 적절한 위치에서 변형을 수행 할 수 있습니다.

링크가 말했듯이 :

나무가 숲에 떨어지면 소리가 나나요? 순수한 함수가 변경 불가능한 반환 값을 생성하기 위해 일부 로컬 데이터를 변경하면 괜찮습니까?


2

지역 변경 가능 변수가 없으면 한 가지 장점이 있습니다. 스레드에 대해 더 친숙한 기능입니다.

확률이 낮은 데이터 손상을 일으키는 로컬 변수 (코드가 아니거나 소스가 없음)에 의해 화상을 입었습니다. 스레드 안전성은 어떤 식 으로든 언급되지 않았으며, 호출에 걸쳐 지속되는 상태는 없었으며 부작용은 없었습니다. 스레드 안전하지 않을 수도 있지만 100,000 개의 임의 데이터 손상 중 1을 쫓는 것은 큰 고통입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.