객체가 변경 가능한 경우 함수형 프로그래밍의 맥락에서 무엇이 잘못 될 수 있습니까?


9

불변 객체와 같은 불변 객체와 불변 객체의 이점은 공유 및 쓰기 가능한 상태로 인해 다중 스레드 프로그래밍 문제를 해결하는 데 많은 어려움을 겪는 것을 볼 수 있습니다. 반대로, 가변 객체는 매번 새로운 사본을 생성하지 않고 객체의 신원을 처리하는 데 도움이되므로 특히 큰 객체의 성능 및 메모리 사용량을 향상시킵니다.

내가 이해하려고하는 한 가지는 함수형 프로그래밍의 맥락에서 가변 객체를 가질 때 무엇이 ​​잘못 될 수 있는지입니다. 나에게 들었던 요점 중 하나는 다른 순서로 함수를 호출 한 결과가 결정적이지 않다는 것입니다.

함수 프로그래밍에서 가변 객체를 사용하여 무엇이 잘못 될 수 있는지가 분명한 실제 예를 찾고 있습니다. 기본적으로 나쁘면 OO 또는 함수형 프로그래밍 패러다임에 관계없이 나쁘다.

나는 내 자신의 진술 자체 가이 질문에 답한다고 믿는다. 그러나 여전히 자연스럽게 느낄 수 있도록 몇 가지 예가 필요합니다.

OO는 캡슐화, 다형성 등과 같은 도구를 사용하여 종속성을 관리하고 더 쉽고 유지 관리 가능한 프로그램을 작성하는 데 도움이됩니다.

함수형 프로그래밍은 유지 관리 가능한 코드를 홍보하는 동일한 동기를 갖지만 스타일을 사용하여 OO 도구 및 기술을 사용할 필요가 없습니다. 부작용 중 하나 인 기능, 순수 기능 등을 최소화하는 것으로 생각합니다.


1
내가 말하고 싶지만 @Ruben 대부분의 기능적인 언어 변경 가능한 변수를 허용 할 수 있지만, 다른 예를 들어, 변경 가능한 변수는 다른 유형이 사용할 수 있도록
JK합니다.

1
첫 번째 단락에서 불변과 가변을 혼합 할 수 있다고 생각합니까?
jk.

1
@jk., 그는 확실히했다. 수정하도록 수정했습니다.
David Arno

6
@Ruben 함수형 프로그래밍 은 패러다임입니다. 따라서 기능적 프로그래밍 언어가 필요하지 않습니다. 그리고 F #과 같은 일부 fp 언어에는 이 기능이 있습니다 .
Christophe

1
@Ruben은 특별히 haskell hackage.haskell.org/package/base-4.9.1.0/docs/ 에서 Mvars를 생각하지 않았습니다.… 언어마다 다른 해결책이 있거나 IORefs hackage.haskell.org/package/base-4.11.1.0 /docs/Data-IORef.html 물론 monads
jk

답변:


7

OO 접근 방식과 비교하여 중요성을 가장 잘 보여줍니다.

예를 들어 물체가 있다고

Order
{
    string Status {get;set;}
    Purchase()
    {
        this.Status = "Purchased";
    }
}

OO 패러다임에서이 방법은 데이터에 첨부되며 해당 데이터가 해당 방법에 의해 변경되는 것이 좋습니다.

var order = new Order();
order.Purchase();
Console.WriteLine(order.Status); // "Purchased"

기능적 패러다임에서는 결과를 기능 측면에서 정의합니다. 구매 한 순서 IS 순서인가 구매 함수의 결과. 이것은 우리가 확인해야 할 몇 가지 사항을 의미합니다

var order = new Order(); //this is a 'new order'
var purchasedOrder = purchase(order); // this is a 'purchased order'
Console.WriteLine(order.Status); // "New" order is still a 'new order'

order.Status == "구매"를 원하십니까?

또한 우리의 기능이 dem 등하다는 것을 의미합니다. 즉. 두 번 실행하면 매번 같은 결과가 나옵니다.

var order = new Order(); //new order
var purchasedOrder = purchase(order); //purchased order
var purchasedOrder2 = purchase(order); //another purchased order
var purchasedOrder = purchase(purchasedOrder); //error! cant purchase an order twice

구매 기능으로 주문을 변경 한 경우 purchaseOrder2가 실패합니다.

사물을 함수의 결과로 정의하면 실제로 계산하지 않고도 해당 결과를 사용할 수 있습니다. 프로그래밍 용어로는 실행이 지연됩니다.

이것은 그 자체로는 유용 할 수 있지만, 함수가 실제로 언제 일어날 지 확신하지 못하고 그것에 대해 괜찮다면 병렬 처리를 OO 패러다임에서 할 수있는 것보다 훨씬 더 많이 활용할 수 있습니다.

함수를 실행해도 다른 함수의 결과에는 영향을 미치지 않습니다. 따라서 원하는만큼 많은 스레드를 사용하여 컴퓨터가 원하는 순서대로 실행되도록 할 수 있습니다.

함수가 입력을 변경하면 그러한 것들에 대해 훨씬 더 조심해야합니다.


감사 !! 매우 도움이됩니다. 따라서 새로운 구매 구현은 Order Purchase() { return new Order(Status = "Purchased") } 상태가 읽기 전용 필드 인 것처럼 보입니다 . ? 함수 프로그래밍 패러다임과 관련하여 왜이 관행이 더 관련이 있는가? 언급 한 이점은 OO 프로그래밍에서도 볼 수 있습니다.
rahulaga_dev

OO에서는 object.Purchase ()가 객체를 수정할 것으로 예상합니다. 당신은 그것을 불변으로 만들 수 있지만, 완전한 기능적 패러다임으로 이동하지 않는 이유
Ewan

본질적으로 객체 지향적 인 순수한 C # 개발자이기 때문에 문제를 시각화해야한다고 생각합니다. 따라서 함수형 프로그래밍을 포함하는 언어로 말하는 것은 구매 주문을 반환하는 'Purchase ()'함수가 클래스 또는 객체에 첨부되도록 요구하지 않습니다.
rahulaga_dev

3
함수형 c #을 쓰면 객체로 구조체를 변경하고, 불변으로 만들고 Func <주문, 주문> 구매
Ewan

12

불변의 객체가 유익한 이유를 이해하는 열쇠는 실제로 기능 코드에서 구체적인 예를 찾으려고하는 것은 아닙니다. 대부분의 기능 코드는 기능 언어를 사용하여 작성되고 대부분의 기능 언어는 기본적으로 변경 불가능하므로 패러다임의 본질은 사용자가 찾고있는 것을 발생하지 않도록 설계되었습니다.

가장 중요한 것은 불변성의 이점은 무엇입니까? 답은 복잡성을 피한다는 것입니다. 우리는 두 개의 변수를 가지고, 말 xy. 둘 다의 값으로 시작합니다 1. y13 초마다 두 배가됩니다. 각각의 가치는 20 일 안에 무엇입니까? x될 것 1입니다. 쉽습니다. y좀 더 복잡하기 때문에 해결하려면 노력이 필요합니다. 20 일 동안 하루 중 몇시입니까? 일광 절약 시간제를 고려해야합니까? 의 복잡성 y에 비해는 x훨씬 더 많은 단지입니다.

그리고 이것은 실제 코드에서도 발생합니다. 믹스에 뮤팅 값을 추가 할 때마다 코드를 쓰거나 읽거나 디버깅하려고 할 때 머리 나 종이에 들고 계산할 수있는 또 다른 복잡한 값이됩니다. 복잡성이 높을수록 실수를하거나 버그를 도입 할 가능성이 커집니다. 코드는 작성하기 어렵다; 읽기 어렵다; 디버깅하기 어렵다 : 코드를 제대로 얻기가 어렵다.

그러나 돌연변이는 나쁘지 않습니다. 변경 가능성이 0 인 프로그램은 결과가 없을 수 있으며 이는 쓸모가 없습니다. 변경 가능성이 결과를 화면, 디스크 또는 다른 곳에 쓰는 경우에도 거기에 있어야합니다. 나쁜 점은 불필요한 복잡성입니다. 복잡성을 줄이는 가장 간단한 방법 중 하나는 기본적으로 사물을 변경할 수 없게 만들고 성능이나 기능상의 이유로 인해 필요할 때만 변경할 수 있도록하는 것입니다.


4
"복잡성을 줄이는 가장 간단한 방법 중 하나는 기본적으로 사물을 변경할 수 없게 만들고 필요할 때만 변경할 수 있도록하는 것입니다.": 아주 훌륭하고 간결한 요약.
Giorgio

2
@DavidArno 설명하는 복잡성은 코드를 추론하기 어렵게 만듭니다. "코드를 작성하기가 어렵고, 읽기가 어렵고, 디버그하기가 어렵다 ..."라고 말할 때이 문제를 다루었습니다. 나는 불변의 객체를 좋아한다. 코드는 스스로뿐만 아니라 전체 프로젝트를 알지 못하는 채 관찰하는 관찰자가 훨씬 쉽게 추론 할 수 있기 때문이다.
분해-번호 -5

1
@RahulAgarwal, " 그러나 왜이 문제가 함수형 프로그래밍의 맥락에서 더 두드러지게 나타 납니까 ? " 그렇지 않습니다. FP가 불변성을 장려하여 문제를 피하기 때문에 FP에서 문제가 덜 두드러지기 때문에 당신이 묻는 것에 혼란 스러울 것입니다.
David Arno

1
@djechlin, " 어떻게 당신의 13 초 예제를 불변 코드로 분석하기 쉽게 만들 수 있습니까? "그것은 할 수 없습니다 : y변경해야합니다; 그것은 요구 사항입니다. 때로는 복잡한 요구 사항을 충족하기 위해 복잡한 코드가 있어야합니다. 내가 만들려고했던 요점은 불필요한 복잡성을 피해야한다는 것입니다. 변경 값은 본질적으로 고정 값보다 복잡하므로 불필요한 복잡성을 피하기 위해 필요할 때만 값을 변경합니다.
David Arno

3
돌연변이는 정체성의 위기를 만듭니다. 변수에 더 이상 단일 ID가 없습니다. 대신, 그 정체성은 이제 시간에 달려 있습니다. 따라서 단일 x 대신 상징적으로 x_t 패밀리가 생겼습니다. 해당 변수를 사용하는 모든 코드는 이제 시간에 대해서도 걱정해야하므로 답변에 언급 된 추가 복잡성이 발생합니다.
Alex Vong

8

함수형 프로그래밍의 맥락에서 잘못 될 수있는 것

비 기능적 프로그래밍에서 잘못 될 수있는 것과 동일한 것 : 원치 않는 예기치 않은 부작용 이 발생할 수 있으며, 이는 범위가 지정된 프로그래밍 언어의 발명 이후 잘 알려진 오류의 원인입니다.

기능적 프로그래밍과 비 기능적 프로그래밍의 차이점에 대한 유일한 차이점은 비 기능적 코드에서는 일반적으로 기능적 프로그래밍에서 부작용을 기대할 수 있다는 것입니다.

기본적으로 나쁘면 OO 또는 함수형 프로그래밍 패러다임에 관계없이 나쁘지 않습니까?

물론-원치 않는 부작용은 패러다임에 관계없이 버그 범주입니다. 반대의 경우도 마찬가지입니다. 의도적으로 사용 된 부작용은 성능 문제를 해결하는 데 도움이 될 수 있으며 패러다임에 관계없이 I / O 및 외부 시스템을 처리 할 때 대부분의 실제 프로그램에 일반적으로 필요합니다.


4

방금 귀하의 질문을 상당히 잘 보여주는 StackOverflow 질문에 답변 했습니다. 변경 가능한 데이터 구조의 주요 문제점은 ID가 한 번에 정확한 순간에만 유효하므로 사람들은 코드에서 ID가 일정하다는 것을 알 수있는 작은 지점에 최대한 많이 넣는 경향이 있다는 것입니다. 이 특정 예제에서는 for 루프 내에서 많은 로깅을 수행합니다.

for (elem <- rows map (row => s3 map row)) {
  val elem_str = elem.map(_.toString)

  logger.info("verifying the S3 bucket passed from the ctrl table for each App")
  logger.info(s"Checking on App Code: ${elem head}")

  listS3Buckets(elem_str(1), elem_str(2)) match {

    case Some(allBktsInfo) =>
      logger.info(s"App: ${elem_str head} provided the bucket name as: ${elem_str(3)}")
      if (allBktsInfo.exists(x => x.getName == elem_str(3))) {
        logger.info(s"Provided S3 bucket: ${elem_str(3)} exists")
        println(s"s3 ${elem_str(3)} bucket exists")
      } else {
        logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
        logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
        excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
        println(s"s3 bucket ${elem_str(3)} doesn't exists")
    }

    case None =>
      logger.info(s"WARNING: Provided S3 bucket ${elem_str(3)} doesn't exists")
      logger.info(s"WARNING: Dropping the App: ${elem_str.head} from backup schedule")
      excludeList += elem_str.head // If the bucket is invalid then we exclude from backup
}

불변성에 익숙한 경우 너무 오래 기다리면 데이터 구조가 변경 될 염려가 없으므로 여가 시간에 논리적으로 분리 된 작업을 훨씬 더 분리 된 방식으로 수행 할 수 있습니다.

val (exists, missing) = rows partition bucketExists
missing foreach {row =>
  logger.info(s"WARNING: Provided S3 bucket ${row("s3_primary_bkt_name")} doesn't exist")
  logger.info(s"WARNING: Dropping the App: ${row("app")} from backup schedule")
}

3

불변의 객체를 사용하는 것의 장점은 수신자가 그것을 조사 할 때 특정 속성을 가진 객체에 대한 참조를 수신하고 다른 속성에 동일한 속성을 가진 객체에 대한 참조를 제공해야하는 경우 간단히 전달할 수 있다는 것입니다 다른 사람이 참조를 받았을 있는 대상 또는 오브젝트에 대해 수행 할 수있는 작업 ( 어느 누구도 오브젝트에 대해 수행 할 수있는 작업이 없기 때문에 ) 또는 수신자가 오브젝트를 검사 할 수있는 경우에 관계없이 오브젝트에 대한 참조를 따라 속성은 검사시기에 관계없이 동일합니다].

대조적으로, 수신자가 그것을 검사 할 때 (수신자 자체가 변경되지 않는다고 가정 할 때) 특정 속성을 가질 수있는 변경 가능한 객체에 대한 참조를 누군가에게 제공 해야하는 코드는 수신자 이외의 다른 것이 변경되지 않는다는 것을 알아야합니다. 수신자가 해당 특성에 액세스 할시기를 알고, 마지막으로 수신자가 특성을 조사 할 때까지 해당 특성을 변경할 것이 없음을 알고 있습니다.

나는 함수형 프로그래밍이 아닌 일반적인 프로그래밍이 불변의 객체를 세 가지 범주로 분류하는 것이 가장 도움이된다고 생각합니다.

  1. 참조가 있어도 변경할 수없는 객체입니다. 이러한 객체와 객체에 대한 참조는 값으로 작동 하며 자유롭게 공유 할 수 있습니다.

  2. 참조가 있지만 실제로 변경 되는 코드에 참조가 노출되지 않는 코드로 자신을 변경할 수있는 객체 . 이러한 객체는 값을 캡슐화하지만 값을 변경하거나 코드에 노출되지 않도록 신뢰할 수있는 코드와 만 공유 할 수 있습니다.

  3. 변경 될 객체. 이러한 객체는 컨테이너 로 가장 잘보고 식별자 로 참조합니다 .

유용한 패턴은 종종 개체가 컨테이너를 만들고 나중에 참조를 유지하지 않도록 신뢰할 수있는 코드를 사용하여 컨테이너를 채우고 유니버스의 어느 곳에 나 존재하는 유일한 참조를 코드에서 수정하지 않는 것입니다. 채워지면 개체입니다. 컨테이너는 변경 가능한 유형일 수 있지만 실제로 변경되는 것은 없기 때문에 변경 불가능한 것처럼 (*)에 대해 추론 될 수 있습니다. 컨테이너에 대한 모든 참조가 내용을 변경하지 않는 변경 불가능한 랩퍼 유형으로 유지되는 경우, 랩퍼에 대한 참조가 자유롭게 공유되고 검사 될 수 있기 때문에 해당 랩퍼는 데이터가 변경 불가능한 오브젝트에 보유 된 것처럼 안전하게 전달 될 수 있습니다. 언제든지

(*) 멀티 스레드 코드에서 스레드가 래퍼에 대한 참조를 볼 수 있기 전에 컨테이너에 대한 모든 작업의 ​​영향이 해당 스레드에 표시되도록하려면 "메모리 장벽"을 사용해야 할 수도 있습니다. 그것은 여기서 완전성을 위해 언급 된 특별한 경우입니다.


인상적인 답변 주셔서 감사합니다! 아마도 내 혼란의 근원은 아마도 C # 배경에서 왔고 "C #에서 기능 스타일 코드 작성"을 배우고 있기 때문에 어디에서나 변경 가능한 객체를 피한다고 말하는 것입니다. 적용이 올바른 경우) 불변성.
rahulaga_dev

@RahulAgarwal : 객체에 대한 참조가 동일한 객체에 대한 다른 참조의 존재에 의해 영향을받지 않는 값을 캡슐화하거나 , 동일한 객체에 대한 다른 참조와 연관시킬 수있는 동일성 을 가지거나, 또는 둘 다 불가능한 값을 캡슐화 할 수 있습니다. 실제 상태가 변경되면 해당 상태와 관련된 개체의 값이나 ID가 일정 할 수 있지만 둘 다 변경할 수는 없습니다. 둘 다 변경해야합니다. 50,000 달러는 무엇을해야 하는가.
supercat

1

이미 언급했듯이, 가변 상태의 문제는 기본적으로 부작용 의 더 큰 문제의 하위 클래스이며, 함수의 반환 유형은 함수의 실제 기능을 정확하게 설명하지 않습니다.이 경우 상태 돌연변이도 발생합니다. 이 문제는 F * ( http://www.fstar-lang.org/tutorial/ ) 와 같은 일부 새로운 연구 언어로 해결되었습니다 . 이 언어 는 형식 시스템과 유사한 효과 시스템을 만들어 함수가 형식을 정적으로 선언 할뿐만 아니라 그 효과도 선언합니다. 이러한 방식으로, 함수의 호출자는 함수를 호출 할 때 상태 변이가 발생할 수 있음을 인식하고 그 효과는 호출자에게 전파됩니다.

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