우오오 멋집니다! C#이 사실 많이 현대화 되었지요.
그렇습니다.
그런데, F# 코드를 보니, 아래 패턴 매칭에 대한 지원이 있으면, 좋을 것 같다는 생각을 해봅니다.
// => source.Any() is false ? []
=> source is [] ? []
사실, Linq 확장 메서드에 IsEmpty()가 없어서, 항상 별도로 정의해서 쓰곤 했거든요.
public static bool IsEmpty(this IEnumerable<T> source) => !source.Any();
이렇게 정의하면, 코드 가독성이 좋아집니다.
// => source.Any() is false ? []
=> source.IsEmpty() ? []
이것보다는 패턴 매칭이 더 좋을 것 같습니다.
추가
갑자기, F# 이 List 를 사용했다는 생각이 났습니다. C# 도 List 를 사용하면,
public static List<T> QuickSort<T>(this List<T> source)
where T : IComparable<T> => source is [] ? []
:[
.. source[1..].FindAll(x => x.CompareTo(source[0]) < 0).QuickSort(),
source[0],
.. source[1..].FindAll(x => x.CompareTo(source[0]) >= 0).QuickSort(),
];
F#과 C# 비교 : 웹 페이지 다운로드
사실 본문 내용의 극적인 비교는 C#이 아직 using xxx; 문법을 지원하지 않았던 과거의 기준이기는 합니다.
F#에서 특정 웹 페이지를 다운로드 하고 텍스트 스트림을 처리하는 콜백으로 이를 처리하는 코드를 살펴봅시다.
open System.Net
open System
open System.IO
let fetchUrl callback url =
let req = WebRequest.Create(Uri(url))
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
callback reader url
이에 해당하는 C# 코드는,
class WebPageDownloader
{
public TResult FetchUrl<TResult>(
string url,
Func<string, StreamReader, TResult> callback)
{
var req = WebRequest.Create(url);
using (var resp = req.GetResponse())
{
using (var stream = resp.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
return callback(url, reader);
}
}
}
}
}
으로 가독성이 많이 떨어집니다. 물론,
using (var resp = req.GetResponse())
using (var stream = resp.GetResponseStream())
using (var reader = new StreamReader(stream))
{
}
으로 중괄호를 제거할 수 있지만 보다 일반적인 경우에는 필요합니다.
C#의 최근 문법에는 다음과 같이 쓸 수 있습니다.
using var resp = req.GetResponse(); using var stream = resp.GetResponseStream(); using var reader = new StreamReader(stream);
그런 다음 myCallback
을 다음처럼 작성한 후 사용할 수 있습니다.
let myCallback (reader:IO.StreamReader) url =
let html = reader.ReadToEnd()
let html1000 = html.Substring(0,1000)
printfn "Downloaded %s. First 1000 is %s" url html1000
html
let google = fetchUrl myCallback "http://google.com"
F#의 또다른 유용한 기능으로 `베이크 인" 할 수 있도록 매개변수를 매번 전달하지 않아도 됩니다.
//베이크 인 하여 myCallback를 기본으로 사용하는 새로운 함수를 만듬
let fetchUrl2 = fetchUrl myCallback
/ test
let google = fetchUrl2 "http://www.google.com"
let bbc = fetchUrl2 "http://news.bbc.co.uk"
let sites = ["http://www.bing.com";
"http://www.google.com";
"http://www.yahoo.com"]
sites |> List.map fetchUrl2
이에 동등한 C#코드는 다음과 같습니다.
[Test]
public void TestFetchUrlWithCallback()
{
Func<string, StreamReader, string> myCallback = (url, reader) =>
{
var html = reader.ReadToEnd();
var html1000 = html.Substring(0, 1000);
Console.WriteLine(
"Downloaded {0}. First 1000 is {1}", url,
html1000);
return html;
};
var downloader = new WebPageDownloader();
var google = downloader.FetchUrl("http://www.google.com",
myCallback);
// test with a list of sites
var sites = new List<string> {
"http://www.bing.com",
"http://www.google.com",
"http://www.yahoo.com"};
// process each site in the list
sites.ForEach(site => downloader.FetchUrl(site, myCallback));
}
현대 C#은 이제 F#과 거의 동일한 코드를 생성할 수 있습니다.
var myCallback = (string url, StreamReader reader) =>
{
var html = reader.ReadToEnd();
var html100 = html[..100];
Console.WriteLine($"Downloaded {url}. First 1000 is {html100}");
return html;
};
var fetchUrl2 = (string url) => WebPageDownloader.FetchUrl(url, myCallback);
var google = fetchUrl2("http://www.google.com");
var bbc = fetchUrl2("http://news.bbc.co.uk");
List<string> sites = [
"http://www.bing.com",
"http://www.google.com",
"http://www.yahoo.com"];
sites.ForEach(i => fetchUrl2(i));
현대 C#에 이르러 그 격차가 줄긴 했지만 C#의 발전 방향이 F#의 장점을 쫓고 있었음을 알 수 있었네요.
F#의 4가지 핵심 개념
내용을 이해하는 것과 이해한 것으로 할 수 있는 것은 하늘과 땅 차이입니다. 슬슬 F#의 장점과 특징이 파악되고 있습니다. 벌써 며칠이 지났군요!
F#은 강력한 함수형 언어로 구사할 수 있기 때문에 다음의 특징을 살펴봐야 합니다.
- 객체 지향 보다는 함수 지향
- Statement 보다는 Expression
코드 단위인 Expression과 Statement의 차이를 알아보자 - Parkito's on the way - 도메인 모델을 위한 대수 유형
- 제어 흐름을 위한 패턴 매칭
C# 언어도 이제 함수형 언어의 여러 장점을 취하고 있습니다. 그중 패턴 매칭도 있습니다.
객체 지향 보다는 함수 지향
객체 지향 패러다임은 역할을 개체
에 두자는 것입니다. 그래서 상태와 행위가 개체
라는 오브젝트로 존재하게 됩니다. 이에 반해 함수 지향은 데이터
가 중심이 되어서 데이터의 흐름에 집중 합니다. 그리고 다음의 특징을 통해 문제를 풉니다.
- 결합(Composition)
함수 지향 프로그래밍은 작은 단위를 결합하여 큰 문제를 푸는데 적합합니다. 이는 객체 지향의 인터페이스를 통한 결합과 유사한 점이 있습니다. - 분해와 리팩토링
함수 지향은 객체 지향으로 나눌 수 있는 것 이상으로 함수 단위로 나눌 수 있어서 중복 코드를 좀 더 억제하고 코드 읽기가 편리 합니다. - 좋은 디자인
함수형 접근 방식에 의해서 자연스럽게관심사 분리
,단일 책임 원칙
,구현이 아닌 인터페이스
등 좋은 디자인 중 많은 부분은 함수형 접근 방식의 자연스러운 결과입니다.
문장(Statement) 보다는 표현식(Expression)
F#은 함수에 의해 모든 코드 블럭은 값과 깊게 연관되어 있고 값은 반환합니다. 더 큰 코드 블럭은 결합을 통해 더 작은 블럭을 결합하여 만들어집니다.
표현식 기반이란 LINQ와 SQL과 같은 형태입니다.
대수 유형
F#은 단순한 유형을 결합하여 좀 더 복잡한 유형을 만들어 활용합니다. 이는 일반 언어의 struct으로 생각할 수 있는데 유니온 유형의 특징으로 일반 복합 유형과 구분됩니다.
type IntOrBool =
| IntChoice of int
| BoolChoice of bool
let y = IntChoice 42
let z = BoolChoice true
패턴 매칭
패턴 매칭은 모든 조건에 해당하는 값을 처리할 수 있도록 합니다.
match booleanExpression with
| true -> // true branch
| false -> // false branch
match aDigit with
| 1 -> // Case when digit=1
| 2 -> // Case when digit=2
| _ -> // Case otherwise
match aList with
| [] ->
// Empty case
| first::rest ->
이는 강력해서 C#에서도 이제 사용 가능 합니다.
유니온 유형을 사용한 패턴 매칭
이 기능은 매우 강력해서 Rust 등 최신 프로그래밍 언어에서 사용되고 있으며 빠르고 정확하며 효율적인 자료형에 대한 처리를 가능하게 합니다.
type Shape = // define a "union" of alternative structures
| Circle of radius:int
| Rectangle of height:int * width:int
| Point of x:int * y:int
| Polygon of pointList:(int * int) list
let draw shape = // define a function "draw" with a shape param
match shape with
| Circle radius ->
printfn "The circle has a radius of %d" radius
| Rectangle (height,width) ->
printfn "The rectangle is %d high by %d wide" height width
| Polygon points ->
printfn "The polygon is made of these points %A" points
| _ -> printfn "I don't recognize this shape"
let circle = Circle(10)
let rect = Rectangle(4,5)
let point = Point(2,3)
let polygon = Polygon( [(1,1); (2,2); (3,3)])
[circle; rect; polygon; point] |> List.iter draw
행동 지향 대 데이터 지향 디자인
이러한 패턴 매칭은 객체 지향의 관점에서는 모든 유형에 대한 분기가 일어나므로 안티패턴 처럼 보입니다. 객체 지향에서는 추상화를 통해 어떤 유형이든지 동일하게 처리하도록 하는 것을 지향하는데요,
F#에서는 이것을 유니온 타입으로 해결합니다.