이 방법이 순수합니까?


9

다음과 같은 확장 방법이 있습니다.

    public static IEnumerable<T> Apply<T>(
        [NotNull] this IEnumerable<T> source,
        [NotNull] Action<T> action)
        where T : class
    {
        source.CheckArgumentNull("source");
        action.CheckArgumentNull("action");
        return source.ApplyIterator(action);
    }

    private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
        where T : class
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }

반환하기 전에 시퀀스의 각 항목에 동작을 적용합니다.

PureResharper 주석속성 을이 방법에 적용 해야하는지 궁금 하고 인수에 대한 인수를 볼 수 있습니다.

장점 :

  • 엄격하게는, 말하기 입니다 순수; 시퀀스에서 호출하면 시퀀스가 ​​변경되거나 (새 시퀀스가 ​​반환 됨) 관찰 가능한 상태가 변경되지 않습니다.
  • 시퀀스를 열거하지 않으면 효과가 없으므로 결과를 사용하지 않고 호출하는 것은 분명히 실수입니다. 따라서 Resharper에서 경고를 표시하고 싶습니다.

단점 :

  • Apply메소드 자체가 순수 하더라도 결과 시퀀스 열거하면 관찰 가능한 상태 변경 (메소드의 포인트)이 변경됩니다. 예를 들어, items.Apply(i => i.Count++)열거 될 때마다 항목의 값이 변경됩니다. 따라서 순수 속성을 적용하는 것은 아마도 잘못된 것입니다 ...

어떻게 생각해? 속성을 적용해야합니까?


답변:


15

부작용이 없기 때문에 순수하지 않습니다. 구체적 action으로 각 항목을 호출 합니다. 또한 스레드 안전하지 않습니다.

순수 함수의 주요 속성은 여러 번 호출 할 수 있으며 동일한 값을 반환하는 것 외에는 아무것도하지 않는다는 것입니다. 당신의 경우가 아닙니다. 또한 순수하다는 것은 입력 매개 변수 이외의 다른 것을 사용하지 않음을 의미합니다. 즉, 언제든지 스레드에서 호출 할 수 있으며 예기치 않은 동작이 발생하지 않습니다. 다시 말하지만, 그것은 당신의 기능이 아닙니다.

또한 함수 순도는 장단점이 아닙니다. 부작용이있을 수 있다는 단 하나의 의심조차도 순수하지 않은 것으로 충분합니다.

Eric Lippert 가 좋은 지적을합니다. http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx 를 카운터 인수의 일부로 사용하겠습니다 . 특히 라인

순수한 메소드는 순수한 메소드에 들어간 후 작성된 오브젝트를 수정할 수 있습니다.

다음과 같은 메소드를 작성한다고 가정 해 봅시다.

int Count<T>(IEnumerable<T> e)
{
    var enumerator = e.GetEnumerator();
    int count = 0;
    while (enumerator.MoveNext()) count ++;
    return count;
}

첫째, 이것은 GetEnumerator순수한 것으로 가정합니다 (실제로 소스를 찾을 수는 없습니다). 그렇다면 위 규칙에 따라이 메소드는 본문 자체에서 작성된 인스턴스 만 수정하므로 [Pure]로 주석을 달 수 있습니다. 그 후에 우리는 이것을 구성 할 수 있습니다 ApplyIterator.

Count(ApplyIterator(source, action));

제이 구성은 두 경우에도, 순수하지 Count하고 ApplyIterator순수하다. 그러나 나는이 주장을 잘못된 전제로 세우고 있을지도 모른다. 메소드 내에서 생성 된 인스턴스가 순도 규칙에서 제외된다는 생각은 잘못되었거나 적어도 구체적이지 않습니다.


1
+1 기능 순도는 찬반 양론의 문제가 아닙니다. 기능 순도는 사용법과 안전에 대한 힌트입니다. 이상하게도 OP는 넣었 where T : class지만 OP는 단순히 where T : strut순수해야합니다.
ArTs

4
이 답변에 동의하지 않습니다. 전화 sequence.Apply(action)는 부작용이 없습니다. 만약 그렇다면 부작용을 말하십시오. 이제 전화 sequence.Apply(action).GetEnumerator().MoveNext()는 부작용이 있지만 이미 알고있었습니다. 열거자를 변경합니다! sequence.Apply(action)부름 MoveNext이 불결 하기 때문에 불순종으로 여겨 져야하는 이유는 무엇 sequence.Where(predicate)입니까? sequence.Where(predicate).GetEnumerator().MoveNext()모든 것이 불순합니다.
Eric Lippert

@EricLippert 당신은 좋은 지적을 올립니다. 그러나 GetEnumerator를 호출하는 것만으로는 충분하지 않습니까? 그 순수한 것을 고려할 수 있습니까?
Euphoric

@ 유포 릭 (Euphoric) : GetEnumerator열거자를 초기 상태로 할당하는 것 외에, 호출 하는 것이 관찰 할 수있는 부작용은 무엇입니까 ?
Eric Lippert

1
@EricLippert 그렇다면 .NET의 코드 계약에서 Enumerable.Count가 순수하다고 간주되는 이유는 무엇입니까? 링크가 없지만 Visual Studio에서 게임을 할 때 순수한 비 순수를 사용하면 경고가 표시되지만 계약은 Enumerable.Count와 잘 작동합니다.
Euphoric

18

나는 EuphoricRobert Harvey 의 답변에 동의하지 않습니다 . 물론 그것은 순수한 기능입니다. 문제는

반환하기 전에 시퀀스의 각 항목에 동작을 적용합니다.

첫 번째 "그것"이 무엇을 의미하는지 명확하지 않습니다. "그것"이 그 기능 중 하나를 의미한다면, 그것은 옳지 않습니다. 그러한 기능들 중 어느 것도 그렇게하지 않습니다. MoveNext시퀀스의 열거의은을 수행하고, "반환"를 통해 항목을 Current하지를 반환하여 재산.

이러한 시퀀스는 느리게 열거 되고 간절히 열거 되지 않으므로 시퀀스가 반환 되기 전에 작업이 적용되는 것은 아닙니다 Apply. 열거 자에서 호출 된 경우 시퀀스가 ​​리턴 된 조치가 적용됩니다 MoveNext.

아시다시피,이 함수들은 액션과 시퀀스를 수행하고 시퀀스를 반환합니다. 출력은 입력에 따라 달라지며 부작용이 발생하지 않으므로 순수한 기능입니다.

결과 시퀀스의 열거자를 만든 다음 해당 반복기에서 MoveNext를 호출하면 MoveNext 메서드는 동작을 호출하고 부작용을 생성하기 때문에 순수하지 않습니다. 그러나 우리는 이미 MoveNext가 열거자를 변경하기 때문에 순수하지 않다는 것을 알고있었습니다!

이제 귀하의 질문에 관해서는 속성을 적용해야합니다 . 처음 에이 방법을 쓰지 않기 때문에 속성을 적용 하지 않을 것 입니다. 시퀀스에 동작을 적용하려면 다음을 작성하십시오.

foreach(var item in sequence) action(item);

그것은 분명하다.


2
이 방법은 ForEach확장 방법 과 같은 백에 속 한다고 생각 합니다. 확장 방법과 의도적으로 Linq의 일부는 아닙니다. 그 목적은 부작용을 일으키는 것입니다.
Thomas Levesque

1
@ThomasLevesque : 내 충고는 절대 그렇게하지 않는 것입니다 . 쿼리는 질문에 대답 해야 하며 시퀀스를 변경 하지 않아야 합니다 . 그렇기 때문에 쿼리 라고 합니다 . 쿼리 할 때 시퀀스를 변경하는 것은 매우 위험 합니다. 예를 들어 그러한 쿼리가 Any()시간 이 지남에 따라 여러 번 호출되면 어떻게되는지 생각해보십시오 . 작업은 반복해서 수행되지만 첫 번째 항목에서만 수행됩니다! 순서는 일련의 이어야 합니다 . 일련의 동작 을 원하면 을 만드십시오 IEnumerable<Action>.
Eric Lippert

2
이 답변은 물보다 더 많은 물을 흐릿하게 만듭니다. 당신이 말하는 모든 것은 의심의 여지없이 사실이지만 불변성과 순도의 원칙은 저수준의 구현 세부 사항이 아닌 고급 프로그래밍 언어 원칙입니다. 기능 수준 에서 작업하는 프로그래머 는 내부 작업이 순수 한지 여부와 상관없이 코드 가 기능 수준에서 작동 하는 방식에 관심 이 있습니다. 충분히 낮아지면 거의 확실 하지 않습니다 . 우리는 일반적으로 Von Neumann 아키텍처에서 이러한 것들을 실행합니다.
Robert Harvey

2
@ThomasEding :이 메소드는을 호출하지 않으므로 action순도 action는 관련이 없습니다. 호출 하는 것처럼 보이지만action 이 방법은 열거자를 반환하는 방법과 열거 자의 방법을 나타내는 두 가지 방법의 구문 설탕입니다 MoveNext. 전자는 분명히 순수하고 후자는 분명히 그렇지 않습니다. 이런 식으로보십시오 : 그것이 IEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }순수 하다고 말할 수 있습니까? 이것이 바로 그 기능이기 때문입니다.
Eric Lippert

1
@ThomasEding : 뭔가 빠졌습니다. 그것은 반복자가 작동하는 방식이 아닙니다. 이 ApplyIterator메서드는 즉시 반환 합니다 . 반환 된 객체의 열거자를 ApplyIterator처음 호출 할 때까지 본문에 코드 가 실행 되지 않습니다 MoveNext. 이제이 퍼즐에 대한 답을 추론 할 수 있습니다. blogs.msdn.com/b/ericlippert/archive/2007/09/05/… 정답은 다음과 같습니다. blogs.msdn.com/b/ericlippert/archive / 2007 / 09 / 06 /…
Eric Lippert

3

순수한 함수는 아니므로 Pure 속성을 적용하는 것은 잘못된 것입니다.

순수한 함수는 원래 컬렉션을 수정하지 않으며 효과가없는 동작을 전달하는지 여부는 중요하지 않습니다. 부작용을 일으키는 것이기 때문에 여전히 불순한 기능입니다.

함수를 순수하게 만들려면 콜렉션을 새 콜렉션에 복사하고 조치가 새 콜렉션에 적용한 변경 사항을 적용한 후 원래 콜렉션을 변경하지 않고 새 콜렉션을 리턴하십시오.


글쎄요, 같은 아이템으로 새로운 시퀀스를 반환하기 때문에 원래 컬렉션을 수정하지 않습니다. 이것이 내가 순수하게 만드는 것을 고려한 이유입니다. 그러나 결과를 열거 할 때 항목의 상태가 변경 될 수 있습니다.
Thomas Levesque

경우 item참조 형식입니다, 당신이 돌아 오는 경우에도, 원래의 콜렉션을 수정하는 것 item반복자에. 참조 stackoverflow.com/questions/1538301
로버트 하비

1
그가 컬렉션을 깊게 복사 했더라도 여전히 전달되지 않은 action항목을 수정하는 것 외에 부작용이있을 수 있으므로 순수하지는 않습니다 .
Idan Arye

@IdanArye : 사실, 액션 또한 순수해야합니다.
Robert Harvey

1
@IdanArye : ()=>{}Action으로 변환 가능하며 순수한 함수입니다. 출력은 입력에만 의존하며 관찰 가능한 부작용이 없습니다.
Eric Lippert

0

내 의견으로는, Action이 (Action이 아닌) Action을 받는다는 사실은 순수하지 않다.

에릭 리퍼 트도 동의하지 않습니다. "() => {}은 Action으로 변환 가능하며 순수한 함수입니다. 출력은 입력에만 의존하며 관찰 할 수있는 부작용은 없습니다."

ApplyIterator가 대리자를 사용하는 대신 Action이라는 메서드를 호출했다고 상상해보십시오.

Action이 순수하면 ApplyIterator도 순수합니다. Action이 순수하지 않으면 ApplyIterator가 순수 할 수 없습니다.

델리게이트 유형 (실제 값이 아님)을 고려할 때, 우리는 그것이 순수하다는 보장을하지 않으므로 델리게이트가 순수한 경우에만 메소드가 순수한 메소드로 작동합니다. 따라서 실제로 순수하게 만들려면 순수한 대리자를 받아야합니다 (그리고 존재하는 경우 대리자를 [Pure]로 선언하여 PureAction을 가질 수 있음).

다르게 설명하자면, Pure 방법은 동일한 입력이 주어지면 항상 동일한 결과를 제공해야하며 관찰 가능한 변경을 생성해서는 안됩니다. ApplyIterator는 동일한 소스와 델리게이트를 두 번받을 수 있지만 델리게이트가 참조 유형을 변경하는 경우 다음 실행에서 다른 결과가 나타납니다. 예 : 대리자가 item.Content + = "Changed"와 같은 작업을 수행합니다.

따라서 "문자열 컨테이너"(문자열 유형의 Content 속성이있는 객체) 목록에서 ApplyIterator를 사용하면 다음과 같은 원래 값을 가질 수 있습니다.

Test

Test2

첫 번째 실행 후 목록에는 다음이 포함됩니다.

Test Changed

Test2 Changed

그리고 이번이 세 번째입니다.

Test Changed Changed

Test2 Changed Changed

따라서 델리게이트가 순수하지 않고 호출이 3 번 호출되면 호출이 3 번 실행되지 않도록 최적화 할 수 없으므로 목록의 내용이 변경됩니다. 각 실행마다 다른 결과가 생성됩니다.

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