F# 배우기 - slog

우오오 멋집니다! C#이 사실 많이 현대화 되었지요.

1개의 좋아요

그렇습니다.

그런데, 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(),
    ];
3개의 좋아요

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#의 장점을 쫓고 있었음을 알 수 있었네요.

4개의 좋아요

F#의 4가지 핵심 개념

내용을 이해하는 것과 이해한 것으로 할 수 있는 것은 하늘과 땅 차이입니다. 슬슬 F#의 장점과 특징이 파악되고 있습니다. 벌써 며칠이 지났군요!

F#은 강력한 함수형 언어로 구사할 수 있기 때문에 다음의 특징을 살펴봐야 합니다.

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#에서는 이것을 유니온 타입으로 해결합니다.

3개의 좋아요