또 언제 끝날지 모를 여정을 걸어봅니다.
F# 배우기 입니다.
F#이 어떤 언어인가 확인은 위키백과를 참고 합시다.
2005년에 발표한 언어군요. 함수형 언어로만 알고 있었는데 다중 패러다임 언어로 분류 되는군요.
대표 홈페이지가 있습니다.
또 언제 끝날지 모를 여정을 걸어봅니다.
F# 배우기 입니다.
F#이 어떤 언어인가 확인은 위키백과를 참고 합시다.
2005년에 발표한 언어군요. 함수형 언어로만 알고 있었는데 다중 패러다임 언어로 분류 되는군요.
대표 홈페이지가 있습니다.
F#으로 Blazor를 시작할 수 있을까? 안타깝게도 프로젝트 템플릿 중 F#을 지원하지는 않는 것 같다.
관련해서 Bolero 프로젝트를 찾을 수 있었다.
F#으로 SPA를 개발할 수 있는 환경을 제공한다.
이유는 모르겠지만 이미 Ionide for F# 플러그인이 설치되어 있다. C# 개발 환경을 Visual Studio Code에 구성하면 F#도 따라서 구성되는 것인가? 이후 정확히 살펴볼 필요가 있겠다.
위의 글을 통해 그대로 따라 해본다.
가상 환경에서 다음처럼 구성하였다.
dotnet new console -lang "F#" -o HelloWorldFSharp
cd HelloWorldFSharp
code .
launch.json은 다음처럼 구성할 수 있다.
{
"configurations": [
{
"type": "coreclr",
"request": "launch",
"name": "Launch HelloWorldFSharp",
"program": "${workspaceFolder}/bin/Debug/net8.0/HelloWorldFSharp.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "internalConsole"
}
]
}
Start Debugging으로 breakpoint가 잘 적용되는 것을 확인할 수 있다.
F#의 Visual Studio Code 구성이 원활하지 않아 (관련해서 Visual Studio Code로 F#을 코딩하시는 분께는 환경 구성을 어떻게 했는지 댓글로 도움 바랄께요) Visual Studio 환경에서 하는 것으로 변경한다.
https://csrc.kaist.ac.kr/blog/2020/08/12/바이너리-분석에-웬-fsharp/
https://kwangkeunyi.snu.ac.kr/~kwang/paper/maso/1.html
B2R2는 F#으로 만들어진 이진 분석을 위한 알고리즘, 함수 및 도구 모음 – 라이브러리 입니다.
제가 전에 한번 소개했었군요.
그렇기 때문에 REPL도 웹 페이지로 라이브로 사용할 수 있습니다.
라는 제목으로 학습 목적 및 컨텐츠 생성 목표를 설정해야겠다. 우리나라에서 프로그래머가 되려는 일반 학생이 F#을 배우려 할 가능성은 0% 수렴. 왜… F#는 커녕 Unity로 게임 개발하려고 하는 소수의 학생 빼고는 C#을 배울 생각도, 의지도 없고 있는지조차 모른다. F#으로 취업하기는 하늘에 별 따기일 수 있다. F#은 아마도 본인 연구에 활용할 함수형 프로그래밍에 관심을 가진 극소수의 학/석사의 학생들이 접하고자 하는 언어일 것이다.
실제로 취업은 삶에서 중요한 이벤트이다
함수형 언어 및 F#에 대해 흥미와 감을 조금씩 잡게 도운 사이트는 아래와 같다.
대부분의 컨텐츠가 2015년 이전의 글들이라 쪼금 아쉽기는 하지만 학습 후 F#이 어떻게 변화되었는지를 살펴봐야곘다. 그것 빼고는 훌륭한 내용이다.
예를 들어
The 'Why use F#' series | F# for fun and profit
이 글을 쭉 살펴보면 F#에 대해 감을 잡을 수 있다. 이 글 외에도 훌륭한 글들이 많다.
명령형 개념이란 우리가 지금까지 접했던 C/C++/Java/JavaScript/…C#! 언어들이 접근하는 방식으로 코드가 곧 실행되어 결과를 만드는 개념을 말한다. 순수 함수형 언어는 변수(변하는 저장소)도 없고, 루프(반복문)도 없고, 객체도 없다.
물론 F#은 순수 함수형 언어는 아니다. .NET 생태계를 활용해야 하므로 객체지향 용법도 있다.
그렇다면 값이 변하지 않고(불변), 루프도 없고(재귀), 객체도 없고(함수) 어떻게 문제를 풀 수 있을까?
솔직히 나도 아직까지는 잘 모르겠다. 배우고 있는 중이다.
뭐 추측으로는 순수 함수형을 지향하며 예측되는(불변) 함수의 조립으로 문제를 푸는 것이리라
명령형과 반대되는 개념은 선언형이다. 다음의 글을 살펴보자.
그렇다고 한다. 훌룽하게 정리하셨는데 조금 더 글이 짧아도 될뻔했다.
객체지향 프로그래밍이 이전의 절차지향(명령형) 프로그래밍과 완전히 다른 개발 관점이 필요한 것 처럼 함수형 프로그래밍은 문제 해결의 접근 방식이 객체지향 프로그래밍과 다르다.
명령형이 아닌 함수형 스타일로 코딩하려면 함수적 사고가 필요하다. 아래의 시리즈를 살펴보자.
mutable은 초기에는 사용하지 말자.객체지향 언어를 절차지향 처럼 쓸 수 있는 것 처럼 F#은 비함수형 언어처럼 쓸 수 있다. 값이 mutable이 되지 않도록 초기에는 지양하자.
for나 if-then-else를 쓰지 말자재귀와 패턴 매칭을 쓰자.
"hello".length 를 쓰지 않고 String.length "hello"를 대신 쓰자개체의 사용이 아니므로 그렇다고 한다. (정확한 건 좀 더 학습한 뒤 수정하는 것으로)
이 말은 역으로 F#이 클래스를 만들 수 있다는 것이다. 어쨌든 순수 함수형을 지향에서 튜플, 레코드, 유니언 등으로 문제를 풀어야 한다.
F#은 컴파일러가 좀 더 엄격하므로 (이것도 더 학습한 뒤 수정하는 것으로)
반면에:
가볍고 쉬우며, 도메인 모델을 쉽게 문서화하고 정확성을 보장한다.
list및 seq유형과 관련해서 라이브러리 모듈을 숙달함List.fold및 List.map등의 함수는 강력하다. 이해하면 다양한 곳에서 활용할 수 있다.
꼬리 재귀인지 확인해야 하며 대부분 List.fold로 문제를 풀 수 있음
F#은 f(g(x))보다 깔끔하게 파이프와 컴포지현으로 표혈할 수 있다.
(… 무슨 말인지 모르겠음 이해하고 수정할 것)
부분적용 전에 커링을 먼저… ![]()
조금 더 의견 부탁드릴께요 ^^ 편하게 말씀해주세요~
… 그렇다고 합니다. 아직은 전반적인 언어의 특징 및 사용법을 이해한 단계가 아니기 때문에 흔적으로 기록…
C#과의 중요한 차이점
let myInt = 5
let myFloat = 3.14
let myString = "hello"
기본적으로 불변으로 정의 됩니다.
let twoToFive = [2; 3; 4; 5]
let twoToFive2 = [
2
3
4
5
]
let twoToFive3 = [2..5]
let twoToFive4 = [for i in 2..5 -> i]
printfn "%A" twoToFive
printfn "%A" twoToFive2
printfn "%A" twoToFive3
printfn "%A" twoToFive4
결과는? 모두 [2; 3; 4; 5] 이다.
(물론 어떤 것이 연산 결과가 컴파일 타임인지 런타임인지는 아직은 잘 모르겠다.)
그리고… [1;2;3] 이런 표현은 일반적인 연속 배열이 아니라 링크드리스트이다!
(물론 내부적으로 연속 배열들의 집합이 되도록 최적화 되긴 할 것이다. 잘 모르겠다)
일반 연속 배열은 [|1; 2; 3|] 라고 사용한다.
let oneToFive = 1 :: twoToFive
printfn "%A" oneToFive
앞에 1을 추가하여 새로운 목록을 생성한다. 새로 생성한 목록은 내부적으로 복사가 아니라 링크드리스트로 참조하여 빠르게 생성한다.
let zeroToFive = [0;1] @ twoToFive
두 목록을 연결해서 새로운 목록을 만들 수도 있다.
let square x = x * x
square 3
let add x y = x + y
add 2 3
함수를 이렇게 만들고 사용할 수 있다.
let evens list =
let isEven x = x%2 = 0
List.filter isEven list
evens oneToFive
함수 안에 함수를 사용해서 코드를 줄이거나 가독성을 올릴 수 있다.
let sumOfSquaresTo100 =
List.sum ( List.map square [1..100] )
이렇게 함수를 만들 수 있지만 파이프를 이용하는 것이 좀 더 자연스럽다.
let sumOfSquaresTo100piped =
[1..100] |> List.map square |> List.sum
square 함수 대신 람다 함수로 바로 표현할 수 도 있다.
let sumOfSquaresTo100withFun =
[1..100] |> List.map (fun x->x*x) |> List.sum
패턴 매칭은 원래 F#에서 제공되던 것이다.
let simplePatternMatch =
let x = "a"
match x with
| "a" -> printfn "x is a"
| "b" -> printfn "x is b"
| _ -> printfn "x is something else"
F#에서는 Option 타입에 의해 값이 있을수도 있고 없을 수도 있는 상태를 좀 더 명확하게 표현할 수 있다.
let validValue = Some(99)
let invalidValue = None
Option으로 다음과 같이 패턴 매칭을 할 수 있다.
let optionPatternMatch input =
match input with
| Some i -> printfn "input is an int=%d" i
| None -> printfn "input is missing"
optionPatternMatch validValue
optionPatternMatch invalidValue
튜플은 컴마로 표현한다.
let twoTuple = 1,2
let threeTuple = "a",2,true
레코드 유형은 이름 필드로 세미콜론으로 구분하여 표현한다.
type Person = {First:string; Last:string}
let person1 = {First="john"; Last="Doe"}
C#에서 그렇게 사용하고 싶은 유니온 유형이다.
type Temp =
| DegreesC of float
| DegreesF of float
let temp = DegreesF 98.6
Temp 유형은 DegreesC일 수도 있고 DegreesF일 수도 있다.
다음과 같이 유형은 재귀적으로 정의될 수 도 있다.
type Employee =
| Worker of Person
| Manager of Employee list
let jdoe = {First="John";Last="Doe"}
let worker = Worker jdoe
출력은 %A을 줘서 값을 이쁘게 출력할 수 있다.
printfn "Printing an int %i, a float %f, a bool %b" 1 2.0 true
printfn "A string %s, and something generic %A" "hello" [1;2;3;4]
printfn "twoTuple=%A,\nPerson=%A,\nTemp=%A,\nEmployee=%A"
twoTuple person1 temp worker
C# 개발자가 F#의 간단 명료한 코드의 매력을 느낄 수 있도록 계속 분석합니다.
슬로그 중의 타이틀과 원 내용은 https://fsharpforfunandprofit.com/ 의 내용입니다.
F#으로 1부터 n까지의 제곱의 합을 구하는 코드는 무엇일까요?
let square x = x * x
let sumOfSquares n =
[1..n] |> List.map square |> List.sum
let result = sumOfSquares 100
printfn "%A" result
이에 해당하는 C# 코드는 무엇일까요?
public static class SumOfSquaresHelper
{
public static int Square(int i)
{
return i * i;
}
public static int SumOfSquares(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += Square(i);
}
return sum;
}
}
F#은 C#에 비해 다음의 특징이 있습니다.
물론 최신 C#은 위의 코드 보다 좀 더 F# 처럼 가능합니다.
var square = (int x) => x * x;
var sumOfSquares = (int n) =>
Enumerable.Range(1, n).Select(square).Sum();
var result = sumOfSquares(100);
Console.WriteLine(result);
LINQ와 그 Fluent API 형태, 람다 등 함수형 언어에서 많이 영향 받았다라는 것을 알 수 있군요!
C# 코드가 좀 더 간결해지긴 했지만 여전히 F# 코드가 좀 더 간결한 것 같습니다. 어쨌든 C#도 많이 간결해졌네요!
퀵소트를 통해 F#의 구현과 C#의 구현 차이를 살펴봅시다!
알고리즘은 언어 별 구현된 코드를 비교하는 목적으로 단순화 되었습니다.
다음으로 퀵소트 로직을 설명할 수 있습니다.
이 알고리즘을 F# 코드로 변환하면 다음과 같습니다.
let rec quicksort list =
match list with
| [] -> []
| firstElem::otherElements ->
let smallerElements =
otherElements
|> List.filter (fun e -> e < firstElem)
|> quicksort
let largerElements =
otherElements
|> List.filter (fun e -> e >= firstElem)
|> quicksort
List.concat [smallerElements; [firstElem]; largerElements]
printfn "%A" (quicksort [1;5;23;18;9;1;3])
이에 반해 C# 코드는 다음과 같습니다.
public class QuickSortHelper
{
public static List<T> QuickSort<T>(List<T> values)
where T : IComparable
{
if (values.Count == 0)
{
return new List<T>();
}
T firstElement = values[0];
var smallerElements = new List<T>();
var largerElements = new List<T>();
for (int i = 1; i < values.Count; i++)
{
var elem = values[i];
if (elem.CompareTo(firstElement) < 0)
{
smallerElements.Add(elem);
}
else
{
largerElements.Add(elem);
}
}
var result = new List<T>();
result.AddRange(QuickSort(smallerElements.ToList()));
result.Add(firstElement);
result.AddRange(QuickSort(largerElements.ToList()));
return result;
}
}
알고리즘 로직을 코드로 전환한 것은 C# 코드에 비해 F# 코드가 더 나아 보입니다. (물론, C#의 LINQ를 사용하지 않았기 때문에 조금은 불공평한 비교 같기는 하네요)
다음 코드는 LINQ와 확장 메서드를 이용한 좀 더 현대적(함수형 스타일)인 C# 입니다.
public static class QuickSortExtension
{
public static IEnumerable<T> QuickSort<T>(
this IEnumerable<T> values) where T : IComparable
{
if (values == null || !values.Any())
{
return new List<T>();
}
var firstElement = values.First();
var rest = values.Skip(1);
var smallerElements = rest
.Where(i => i.CompareTo(firstElement) < 0)
.QuickSort();
var largerElements = rest
.Where(i => i.CompareTo(firstElement) >= 0)
.QuickSort();
return smallerElements
.Concat(new List<T>{firstElement})
.Concat(largerElements);
}
}
참고로 다음처럼 F# 코드를 좀 더 간결하게 표현할 수 있습니다.
let rec quicksort2 = function
| [] -> []
| first::rest ->
let smaller,larger = List.partition ((>=) first) rest
List.concat [quicksort2 smaller; [first]; quicksort2 larger]
F# 코드가 C# 코드보다 나은 이유는 로직을 거의 그대로 코드로 변환할 수 있다는 점으로 선언적(“무엇을 해야 하는지”)에 더 집중할 수 있게 하고 명령적(“어떻게 해야 하는지”)에 덜 집중하게 합니다.
왠지 C#이 진 듯하여 ^^; (죄송)
public static class IEnumerableExtensions
{
public static IEnumerable<T> QuickSort<T>(this IEnumerable<T> source)
where T : IComparable<T> => source.Any() is false ? []
: [
..source.Skip(1).Where(x => x.CompareTo(source.First()) < 0 ).ToList().QuickSort(),
source.First(),
..source.Skip(1).Where(x => x.CompareTo(source.First()) >= 0 ).ToList().QuickSort()
];
}