스위치 / 패턴 매칭 아이디어
나는 최근에 F #을보고 있었고, 곧 울타리를 뛰어 넘지 않을 것이지만, C # (또는 라이브러리 지원)이 삶을 더 쉽게 만들 수있는 일부 영역을 분명히 강조합니다.
특히 F #의 패턴 일치 기능에 대해 생각하고 있는데, 이는 현재 스위치 / 조건부 C #에 비해 훨씬 더 풍부한 구문을 허용합니다. 나는 직접적인 예를 제시하려고하지 않을 것입니다 (F #은 그것에 달려 있지 않습니다).
- 유형별 일치 (구분 된 노조에 대한 전체 범위 검사 포함) [이것은 또한 바인딩 된 변수의 유형을 유추하여 멤버 액세스를 제공함]
- 술어와 일치
- 위의 조합 (및 내가 모르는 다른 시나리오)
C #이 결국이 풍부함을 빌려주는 것이 좋을지 모르지만 그 동안 런타임에 수행 할 수있는 작업을 살펴 보았습니다. 예를 들어, 몇 가지 개체를 함께 사용하여 허용하는 것은 매우 쉽습니다.
var getRentPrice = new Switch<Vehicle, int>()
.Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
.Case<Bicycle>(30) // returns a constant
.Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
.Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
.ElseThrow(); // or could use a Default(...) terminator
여기서 getRentPrice는 Func <Vehicle, int>입니다.
[참고-어쩌면 여기 스위치 / 케이스가 잘못된 용어 일 수 있지만 아이디어가 표시됩니다.]
나에게 이것은 반복되는 if / else 또는 복합 삼항 조건을 사용하는 것보다 훨씬 명확합니다 (사소한 표현-대괄호가 무척 까다 롭습니다). 또한 많은 캐스팅을 피하고 VB Select ... Case "x To y와 비슷한 InRange (...) 일치와 같이보다 구체적인 일치로 간단한 확장 (직접 또는 확장 방법을 통해)을 허용합니다. "사용법.
사람들이 위와 같은 구문의 이점이 있다고 생각하는지 (언어 지원이없는 경우) 측정하려고합니까?
또한 위의 3 가지 변형을 가지고 놀았습니다.
- 평가를위한 Func <TSource, TValue> 버전-복합 삼항 조건문과 비교
- Action <TSource> 버전-if / else if / else if / else if / else와 비교
- Expression <Func <TSource, TValue >> 버전-첫 번째 버전이지만 임의의 LINQ 공급자가 사용할 수 있음
또한 Expression 기반 버전을 사용하면 Expression-tree를 다시 작성하여 반복적으로 호출하지 않고 모든 분기를 단일 복합 조건부 Expression으로 인라인 할 수 있습니다. 최근에 확인하지는 않았지만 초기 Entity Framework 빌드에서는 InvocationExpression을별로 좋아하지 않았기 때문에 이것이 필요하다는 것을 상기하는 것 같습니다. 또한 반복되는 델리게이트 호출을 피하기 때문에 LINQ-to-Objects를보다 효율적으로 사용할 수 있습니다. 테스트는 동등한 C #에 비해 동일한 속도 (실제로 더 빠른 속도)로 수행하는 위와 같은 식 (표현식 사용)을 보여줍니다. 복합 조건문. 완벽을 기하기 위해 Func <...> 기반 버전은 C # 조건문보다 4 배나 걸렸지 만 여전히 매우 빠르며 대부분의 사용 사례에서 큰 병목이되지는 않습니다.
위의 (또는 풍부한 C # 언어 지원 가능성에 대한 생각 / 입력 / 비판 등)을 환영합니다.
나는 그것이 오래된 주제라는 것을 알고 있지만 C # 7에서는 할 수 있습니다 :
switch(shape)
{
case Circle c:
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("<unknown shape>");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
C #에서 이러한 "기능적"작업을 시도한 후에 (그리고 그에 대한 책을 시도한 후에), 몇 가지 예외를 제외하고는 그러한 일이 너무 도움이되지 않는다는 결론에 도달했습니다.
주된 이유는 F #과 같은 언어가 이러한 기능을 지원함으로써 많은 힘을 얻었 기 때문입니다. "당신이 할 수있는"것이 아니라 "단순하다, 분명하다, 예상된다".
예를 들어, 패턴 일치에서는 불완전한 일치가 있는지 또는 다른 일치가 적중하지 않을 때 컴파일러에게 알려줍니다. 이것은 개방형 유형에서는 유용하지 않지만 차별적 인 노동 조합 또는 튜플을 일치시킬 때 매우 유용합니다. F #에서는 사람들이 패턴 일치를 기대하고 즉시 의미가 있습니다.
"문제"는 기능적 개념을 사용하기 시작한 후에는 계속하는 것이 당연하다는 것입니다. 그러나 C #에서 튜플, 함수, 부분 메소드 적용 및 카레 링, 패턴 일치, 중첩 함수, 제네릭, 모나드 지원 등을 활용하면 매우 추악하고 매우 빠릅니다. 재미 있고 똑똑한 사람들이 C #에서 아주 멋진 일을 해왔지만 실제로 사용 하는 것은 무겁습니다.
C #에서 (프로젝트 간) 자주 사용하는 결과 :
- IEnumerable을위한 확장 메소드를 통한 시퀀스 함수. C # 구문이 잘 지원하기 때문에 ForEach 또는 Process ( "Apply"?-열거 된 순서 항목에 대한 작업 수행)와 같은 것이 적합합니다.
- 일반적인 진술 패턴을 추상화합니다. 복잡한 try / catch / finally 블록 또는 기타 관련 (종종 매우 일반적인) 코드 블록. LINQ-to-SQL 확장도 여기에 해당됩니다.
- 튜플.
** 그러나 참고 : 자동 일반화 및 형식 유추가 없으면 이러한 기능을 사용하는 데 방해가됩니다. **
다른 사람이 언급했듯이 소규모 팀에서는 특정 목적을 위해 C #을 고수하면 도움이 될 수 있습니다. 그러나 내 경험상 그들은 일반적으로 YMMV보다 더 번거 로움을 느꼈습니다.
다른 링크들 :
- Mono.Rocks 놀이터 에는 비슷한 기능이 많이 있습니다 (비 기능적 프로그래밍이지만 유용한 추가 기능 포함).
- Luca Bolognese의 기능적 C # 라이브러리
- MSDN의 Matthew Podwysocki의 기능적 C #
C #이 유형을 간단하게 전환하지 못하는 이유는 주로 객체 지향 언어이기 때문에 객체 지향 용어로이를 수행하는 '올바른'방법은 Vehicle에 GetRentPrice 메소드를 정의하는 것입니다. 파생 클래스에서 재정의합니다.
즉, 이러한 유형의 기능을 가진 F # 및 Haskell과 같은 다중 패러다임 및 기능적 언어를 사용하는 데 약간의 시간을 보냈으며 이전에 유용했던 여러 위치를 보았습니다. 스위치를 켜는 데 필요한 유형을 작성하지 않으므로 가상 메소드를 구현할 수 없습니다.) 이는 차별 된 노조와 함께 언어에 환영하는 것입니다.
[편집 : Marc가 단락 될 수 있다고 표시 한 것처럼 성능에 관한 부분을 제거함]
또 다른 잠재적 인 문제는 유용성 문제입니다. 마지막 호출에서 일치하는 조건이 충족되지 않으면 어떤 일이 발생하지만 두 가지 이상의 조건과 일치하면 동작은 무엇입니까? 예외를 던져야합니까? 첫 번째 또는 마지막 경기를 반환해야합니까?
이런 종류의 문제를 해결하는 데 사용하는 방법은 유형을 키로 사용하고 람다를 값으로 사용하여 사전 필드를 사용하는 것입니다. 그러나 이는 콘크리트 유형만을 설명하며 추가 술어를 허용하지 않으므로보다 복잡한 경우에는 적합하지 않을 수 있습니다. [Side note-C # 컴파일러의 출력을 보면 switch 문을 사전 기반 점프 테이블로 자주 변환하므로 유형 전환을 지원할 수없는 적절한 이유는 없습니다.]
언어 확장처럼 작동하는 이러한 종류의 라이브러리는 널리 받아 들여질 것 같지는 않지만 재미있게 플레이 할 수 있으며 이것이 유용한 특정 도메인에서 작업하는 소규모 팀에게 실제로 유용 할 수 있습니다. 예를 들어, 이와 같은 임의의 유형 테스트를 수행하는 수많은 '비즈니스 규칙 / 논리'를 작성하는 경우 어떻게 유용한 지 알 수 있습니다.
이것이 C # 언어 기능 일 가능성이 있는지 전혀 알지 못합니다 (의심스러운 것처럼 보이지만 누가 미래를 볼 수 있습니까?).
참고로 해당 F #은 대략 다음과 같습니다.
let getRentPrice (v : Vehicle) =
match v with
| :? Motorcycle as bike -> 100 + bike.Cylinders * 10
| :? Bicycle -> 30
| :? Car as car when car.EngineType = Diesel -> 220 + car.Doors * 20
| :? Car as car when car.EngineType = Gasoline -> 200 + car.Doors * 20
| _ -> failwith "blah"
라인을 따라 클래스 계층을 정의했다고 가정하면
type Vehicle() = class end
type Motorcycle(cyl : int) =
inherit Vehicle()
member this.Cylinders = cyl
type Bicycle() = inherit Vehicle()
type EngineType = Diesel | Gasoline
type Car(engType : EngineType, doors : int) =
inherit Vehicle()
member this.EngineType = engType
member this.Doors = doors
귀하의 질문에 대답하기 위해, 패턴 매칭 구문 구조가 유용하다고 생각합니다. C #에서 구문 지원이 필요합니다.
Here is my implementation of a class that provides (nearly) the same syntax as you describe
public class PatternMatcher<Output>
{
List<Tuple<Predicate<Object>, Func<Object, Output>>> cases = new List<Tuple<Predicate<object>,Func<object,Output>>>();
public PatternMatcher() { }
public PatternMatcher<Output> Case(Predicate<Object> condition, Func<Object, Output> function)
{
cases.Add(new Tuple<Predicate<Object>, Func<Object, Output>>(condition, function));
return this;
}
public PatternMatcher<Output> Case<T>(Predicate<T> condition, Func<T, Output> function)
{
return Case(
o => o is T && condition((T)o),
o => function((T)o));
}
public PatternMatcher<Output> Case<T>(Func<T, Output> function)
{
return Case(
o => o is T,
o => function((T)o));
}
public PatternMatcher<Output> Case<T>(Predicate<T> condition, Output o)
{
return Case(condition, x => o);
}
public PatternMatcher<Output> Case<T>(Output o)
{
return Case<T>(x => o);
}
public PatternMatcher<Output> Default(Func<Object, Output> function)
{
return Case(o => true, function);
}
public PatternMatcher<Output> Default(Output o)
{
return Default(x => o);
}
public Output Match(Object o)
{
foreach (var tuple in cases)
if (tuple.Item1(o))
return tuple.Item2(o);
throw new Exception("Failed to match");
}
}
Here is some test code:
public enum EngineType
{
Diesel,
Gasoline
}
public class Bicycle
{
public int Cylinders;
}
public class Car
{
public EngineType EngineType;
public int Doors;
}
public class MotorCycle
{
public int Cylinders;
}
public void Run()
{
var getRentPrice = new PatternMatcher<int>()
.Case<MotorCycle>(bike => 100 + bike.Cylinders * 10)
.Case<Bicycle>(30)
.Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
.Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
.Default(0);
var vehicles = new object[] {
new Car { EngineType = EngineType.Diesel, Doors = 2 },
new Car { EngineType = EngineType.Diesel, Doors = 4 },
new Car { EngineType = EngineType.Gasoline, Doors = 3 },
new Car { EngineType = EngineType.Gasoline, Doors = 5 },
new Bicycle(),
new MotorCycle { Cylinders = 2 },
new MotorCycle { Cylinders = 3 },
};
foreach (var v in vehicles)
{
Console.WriteLine("Vehicle of type {0} costs {1} to rent", v.GetType(), getRentPrice.Match(v));
}
}
Pattern matching (as described here), its purpose is to deconstruct values according to their type specification. However, the concept of a class (or type) in C# doesn't agree with you.
There's noting wrong with multi-paradigm language design, on the contrary, it's very nice to have lambdas in C#, and Haskell can do imperative stuff to e.g. IO. But it's not a very elegant solution, not in Haskell fashion.
But since sequential procedural programming languages can be understood in terms of lambda calculus, and C# happens to fit well within the parameters of a sequential procedural language, it's a good fit. But, taking something from the pure functional context of say Haskell, and then putting that feature into a language which is not pure, well, doing just that, will not guarantee a better outcome.
My point is this, what makes pattern matching tick is tied to the language design and data model. Having said that, I don't believe pattern matching to be an useful feature of C# because it does not solve typical C# problems nor does it fit well within the imperative programming paradigm.
IMHO the OO way of doing such things is the Visitor pattern. Your visitor member methods simply act as case constructs and you let the language itself handle the appropriate dispatch without having to "peek" at types.
Although it's not very 'C-sharpey' to switch on type, I know that construct would be pretty helpful in general use - I have at least one personal project that could use it (although its managable ATM). Is there much of a compile performance problem, with the expression tree re-writing?
I think this looks really interesting (+1), but one thing to be careful of: the C# compiler is pretty good at optimising switch statements. Not just for short circuiting - you get completely different IL depending on how many cases you have and so on.
Your specific example does do something I'd find very useful - there is no syntax equivalent to case by type, as (for instance) typeof(Motorcycle)
is not a constant.
This gets more interesting in dynamic application - your logic here could be easily data-driven, giving 'rule-engine' style execution.
You can achieve what you are after by using a library I wrote, called OneOf
The major advantage over switch
(and if
and exceptions as control flow
) is that it is compile-time safe - there is no default handler or fall through
OneOf<Motorcycle, Bicycle, Car> vehicle = ... //assign from one of those types
var getRentPrice = vehicle
.Match(
bike => 100 + bike.Cylinders * 10, // "bike" here is typed as Motorcycle
bike => 30, // returns a constant
car => car.EngineType.Match(
diesel => 220 + car.Doors * 20
petrol => 200 + car.Doors * 20
)
);
It's on Nuget and targets net451 and netstandard1.6
참고URL : https://stackoverflow.com/questions/156467/switch-pattern-matching-idea
'Programing' 카테고리의 다른 글
NW.js, Brackets-Shell 및 Electron의 기능적 차이점은 무엇입니까? (0) | 2020.06.13 |
---|---|
최선의 분산 형 무차별 대입 대책은 무엇입니까? (0) | 2020.06.13 |
트리 데이터 구조를위한 데이터베이스 구조 (0) | 2020.06.13 |
.NET에서 메모리 누수를 찾는 데 유용한 전략과 도구는 무엇입니까? (0) | 2020.06.13 |
유연한 플러그인 아키텍처를 만드는 방법? (0) | 2020.06.13 |