LINQ 와 패턴매칭간의 호환

개인적으로 패턴매칭도 좋아하고 linq의 함수형 표현도 좋아하는데, 쓰다보면 둘이 서로 잘 맞지 않는거같아 17줄짜리 간단한 라이브러리를 만들었습니다.

Naratteu.Linq/Naratteu.Linq/SelectWhen.ipynb at main · naratteu/Naratteu.Linq

자세한 코드와 설명은 위에 적어놨고 NuGet에서 바로 써보실수 있습니다.

linq 매서드체인 스타일을 따라가기는 하나 이걸 linq라 부를수 있을건지는 모르겠네요

1개의 좋아요

저도 이 부분은 불편하다고 생각했어요.
불필요하게 두 번 연산하는 부분이 별로라고 생각했습니다.
딱히 해결책을 고민해보진 않았어요.

Select를 했는데 개수가 변하는 부분이 조금 우려되는 부분입니다 ㅎ

1개의 좋아요

Pair 라는 식별자가 무엇을 의미하는 지 명확하지 않기 때문에, 튜플로 정의하는 게 낫지 않을까 생각됩니다.

또한, 개인적으로는 foreach가 절차형 코드라, 확장 메서드를 아래와 같이 정의할 것 같습니다.

static class IEnumerableExtensions
{
    public static IEnumerable<TResult> While<TSource, TResult>(
        this IEnumerable<TSource> source, 
        Func<TSource, (bool matching, TResult whenMatched)> check) => 
    source.Select(check)
        .Where(t => t.matching)
        .Select(t => t.whenMatched);
}
string?[] numbers = ["1", "", null, "2", "four"];
 
IEnumerable<int> ints = numbers.While(x => (int.TryParse(x, out int n), n));

또한, SelectWhen.Select 는 단순 Select만 다루는데, First, Last, TakeWhile, SkipWhile 도 커버하는 구현도 필요할 것 같습니다.

참고로, 동일하게 IQueryable<T> 도 아래와 같이 정의할 수 있겠지만,

static class IQueryableExtensions
{
    public static IQueryable<TResult> While<TSource, TResult>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, (bool matching, TResult whenMatched)>> check) => 
    source.Select(check)
        .Where(t => t.matching)
        .Select(t => t.whenMatched);
}

실제로 동작 가능한지 - IQueryable 구현자(대표적으로 EF core 제공자)들이 문제 없이 처리하는지는 확인이 필요할 것 같습니다. (int.Parse 는 대부분 지원할 것으로 예상하지만, int.TryParse는 모르겠네요.)

2개의 좋아요

확실히 기존 Select 라는 명명이 가지는 역할을 오버하게됩니다. 다만 코드를 작성하는 과정에서 일단 Select를 작성한 이후 람다식과 Where메서드 체인을 덧붙이는것처럼 일단 Select..를 작성한 이후 람다 내부에 When 조건을 선택적으로 주입하게만드는것이 개발흐름상 자연스러운 경험이 되도록 의도한것입니다. 그리고 이 새롭게 제안하는 복합적인 역할에 적합한 별도의 명명을 도저히 떠올리지 못한것도 있습니다.. ㅋㅋ;

최초구현은 튜플활용에서 발전한것이긴 합니다.
하지만 이 SelectWhen.Select 함수와 SelectWhen.When 함수를 조합해 사용하는것은 별다른 문법적인 제약을 둘수가 없이 순전히 사용자가 제가 의도한 용법을 준수할것이라는 이상적인 상황을 가정한 디자인이기 때문에, 사용자가 자유롭게 사용할 튜플을 그대로 활용할경우 (bool, T) 형태를 반환하는 Enumerable.Select 체인이 의도치않게 SelectWhen.Select로 가로채질 우려가 있기때문에 Select 라는 명명을 바꾸거나 튜플보다 유니크한 객체를 주고받을 필요가 있었습니다.

그리하여 이부분도 결국 이름을 뭐라고 짓느냐가 문제였엇는데, KeyValuePair 에서따와 SelectWhenPairSelectWhen.Pair 로 넣게되었고, 가능하다면 추후 더 좋은 이름으로 개명하는것을 고민하고있습니다.


제가 만든 코드스타일과 구현방법이 실무에 적용되었을때 생산성과 성능상에 실질적인 개선을 제공하는지? linq 함수형 패러다임상 정당한 방식인지? 조차 불분명한 실험적인 라이브러리기 때문에 아직 System.Linq.Enumerable 확장클래스의 변형 이상의 고려는 하지는 않고있습니다. 필요한 다른 메서드는 일단 기존 매서드와 체이닝 하면 될테니까요. 어려워보이지만 IQueryable 에 접목해 EFCore 에서 활용할수 있게된다면 굉장히 멋질거같네요. 우선은 ZLinq 에도 이 Select+When 스타일을 적용해 성능을 더 끌어올려볼 생각입니다.

2개의 좋아요

사실, Linq 에서 임시 객체(불변 객체)로 Map하고, 이를 Filter에 사용하는 방식은 Linq 가 추구하는 혹은 권고된 스타일이라, 저는 애용하는 패턴입니다.

이 패턴은 Linq 체인의 가독성을 높이는 방식이기는 하지만, 지적하신 대로, 비슷한 코드 패턴을 반복하는 것이 귀찮기는 합니다.

sequence.Select(x => ( 튜플 ))
   .Where(t => ...)
   .Select(t => ...)

제가 답변한 코드는 이러한 반복성을 줄이는 것에 지나지 않습니다.

3개의 좋아요