F# 자습서: 전체 스택 F# 앱을 빌드하는 방법
게시 됨: 2022-03-11최근 몇 년 동안 함수형 프로그래밍은 특히 엄격하고 생산적인 패러다임으로 명성을 얻었습니다. 함수형 프로그래밍 언어는 프로그래머 커뮤니티 내에서 주목을 받을 뿐만 아니라 많은 대기업에서도 상업적 문제를 해결하기 위해 함수형 프로그래밍 언어를 사용하기 시작했습니다.
예를 들어, 월마트는 체크아웃 인프라에 JVM 기반 기능적 Lisp 언어인 Clojure를 사용하기 시작했습니다. 대형 전자상거래 플랫폼인 Jet.com(현재 Walmart 소유)은 F#을 사용하여 대부분의 마이크로서비스를 구축합니다. 독점 거래 회사인 Jane Street는 주로 OCaml을 사용하여 알고리즘을 구축합니다.
오늘은 F# 프로그래밍에 대해 알아보겠습니다. F#은 유연성, 강력한 .NET 통합 및 사용 가능한 도구의 고품질로 인해 점점 더 많이 채택되고 있는 함수형 프로그래밍 언어 중 하나입니다. 이 F# 자습서의 목적을 위해 프런트 엔드와 백 엔드 모두에 F#만 사용하여 간단한 웹 서버와 관련 모바일 앱을 빌드합니다.
F#을 선택하는 이유와 F#의 용도는 무엇입니까?
오늘의 프로젝트에서는 F#만 사용할 것입니다. F#을 선택한 언어로 선호하는 몇 가지 이유가 있습니다.
- .NET 통합: F#은 나머지 .NET 세계와 매우 긴밀하게 통합되므로 광범위한 프로그래밍 작업을 해결하기 위해 잘 지원되고 철저하게 문서화된 라이브러리의 대규모 에코시스템에 즉시 액세스할 수 있습니다.
- 간결함: F#은 강력한 형식 유추 시스템과 간결한 구문으로 인해 매우 간결합니다. 프로그래밍 작업은 종종 C#이나 Java보다 F#을 사용하여 훨씬 더 우아하게 해결할 수 있습니다. F# 코드는 비교하면 매우 간소화된 것처럼 보일 수 있습니다.
- 개발자 도구: F#은 .NET 에코시스템을 위한 최고의 IDE 중 하나인 강력한 통합 Visual Studio를 사용합니다. Windows가 아닌 플랫폼에서 작업하는 사람들을 위해 Visual Studio 코드에는 많은 플러그인이 있습니다. 이러한 도구는 F# 프로그래밍을 매우 생산적으로 만듭니다.
F# 사용의 이점에 대해 계속 이야기할 수 있지만 더 이상 고민하지 않고 자세히 살펴보겠습니다!
F# 자습서의 아이디어
미국에는 "It's five o'clock 어딘가"라는 유명한 속담이 있습니다.
세계의 일부 지역에서는 오후 5시가 사회적으로 음료나 전통 차 한 잔을 마시는 것이 가장 이른 시간입니다.
오늘은 이 개념을 바탕으로 애플리케이션을 구축해 보겠습니다. 우리는 주어진 시간에 다양한 시간대를 검색하고 5시가 어디인지 찾아 사용자에게 해당 정보를 제공하는 애플리케이션을 구축할 것입니다.
뒷머리
웹 서버 설정
먼저 시간대 검색 기능을 수행하는 백엔드 서비스를 만드는 것부터 시작하겠습니다. Suave.IO를 사용하여 JSON API를 빌드합니다.
Suave.IO는 간단한 웹 앱을 매우 빠르게 코딩할 수 있는 경량 웹 서버가 있는 직관적인 웹 프레임워크입니다.
시작하려면 Visual Studio로 이동하여 새 F# 콘솔 응용 프로그램 프로젝트를 시작합니다. 이 옵션을 사용할 수 없는 경우 Visual Studio 설치 관리자를 사용하여 F# 기능을 설치해야 할 수 있습니다. 프로젝트 이름을 "FivePM"으로 지정합니다. 애플리케이션이 생성되면 다음과 같이 표시되어야 합니다.
[<EntryPoint>] let main argv = printfn "%A" argv 0 // return an integer exit code
이것은 인수를 출력하고 상태 코드 0으로 종료하는 매우 간단한 시작 코드입니다. 자유롭게 인쇄 문을 변경하고 코드의 다양한 기능을 실험해 보십시오. "%A" 포맷터는 전달하는 모든 유형의 문자열 표현을 인쇄하는 특수 포맷터이므로 정수, 부동 소수점 또는 복잡한 유형까지 자유롭게 인쇄할 수 있습니다. 기본 구문에 익숙해지면 Suave를 설치할 차례입니다.
Suave를 설치하는 가장 쉬운 방법은 NuGet 패키지 관리자를 사용하는 것입니다. 프로젝트 -> NuGet 패키지 관리로 이동하고 찾아보기 탭을 클릭합니다. Suave를 검색하고 설치를 클릭합니다. 패키지 설치를 수락하면 모든 설정이 완료됩니다! 이제 program.fs 화면으로 돌아가서 서버 구축을 시작할 준비가 되었습니다.
Suave를 사용하려면 먼저 패키지를 가져와야 합니다. 프로그램 맨 위에 다음 명령문을 입력하십시오.
open Suave open Suave.Operators open Suave.Filters open Suave.Successful
기본 웹 서버를 구축하는 데 필요한 기본 패키지를 가져옵니다. 이제 기본 코드를 다음으로 교체합니다. 이 코드는 간단한 앱을 정의하고 포트 8080에서 제공합니다.
[<EntryPoint>] let main argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg app 0
F# 구문이나 Suave의 경로 처리기를 정의하는 방법에 익숙하지 않더라도 코드는 매우 간단해 보이지만 코드는 상당히 읽기 쉬워야 합니다. 기본적으로 웹 앱은 200 상태와 "Hello World!"와 함께 반환됩니다. "/" 라우트에서 GET 요청을 받았을 때 문자열입니다. 계속해서 애플리케이션을 실행하고(Visual Studio에서 F5) localhost:8080으로 이동하면 "Hello World!"가 표시되어야 합니다. 브라우저 창에서.
서버 코드 리팩토링
이제 웹 서버가 생겼습니다! 불행히도 많은 일을 하지는 않습니다. 그래서 몇 가지 기능을 제공하겠습니다! 먼저 웹 서버에 대해 걱정하지 않고 일부 기능을 빌드할 수 있도록 웹 서버 기능을 다른 곳으로 이동하겠습니다(나중에 웹 서버에 연결합니다). 따라서 별도의 함수를 정의하십시오.
// We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg app
이제 main 함수를 다음으로 변경하고 올바르게 수행했는지 확인하십시오.
[<EntryPoint>] let main argv = runWebServer argv 0
F5 키를 누르면 "Hello World!"가 표시됩니다. 서버는 이전과 같이 작동해야 합니다.
시간대 가져오기
이제 5시가 되는 시간대를 결정하는 기능을 만들어 봅시다. 모든 시간대를 반복하고 오후 5시에 가장 가까운 시간대를 결정하는 코드를 작성하려고 합니다.
또한, 우리는 오후 5시에 매우 가깝지만 약간 이전(예: 오후 4시 58분)인 시간대를 반환하고 싶지 않습니다. : 00 pm, 그러나 가깝습니다.
시간대 목록을 가져오는 것으로 시작하겠습니다. F#에서는 C#과 잘 통합되기 때문에 매우 쉽습니다. 상단에 "open System"을 추가하고 F# 응용 프로그램을 다음과 같이 변경합니다.
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // Now we iterate through the list and print out the names of the timezones for tz in tzs do printfn "%s" tz.DisplayName 0
애플리케이션을 실행하면 콘솔에 모든 시간대, 오프셋 및 표시 이름 목록이 표시됩니다.
사용자 정의 유형 생성 및 사용
이제 시간대 목록이 있으므로 UTC 오프셋, 현지 시간, 현지 시간으로 오후 5시까지의 거리와 같은 정보가 포함된 보다 유용한 사용자 지정 데이터 유형으로 변환할 수 있습니다. , 등. 이를 위해 기본 함수 바로 위에 사용자 지정 유형을 정의해 보겠습니다.
type TZInfo = {tzName: string; minDiff: float; localTime: string; utcOffset: float}
이제 마지막 단계에서 얻은 시간대 정보를 이 TZInfo 개체 목록으로 변환할 수 있습니다. 다음과 같이 주요 기능을 변경하십시오.
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] printfn "%A" tzList.Head 0
그리고 화면에 인쇄된 Dateline Standard 시간에 대한 tzInfo 개체가 표시되어야 합니다.
정렬과 필터링, 그리고 파이핑, 오 마이!
이제 이러한 tzInfo 객체의 목록이 있으므로 이러한 객체를 필터링하고 정렬하여 1) 오후 5시 이후 및 2) 1)에서 시간대의 오후 5시에 가장 가까운 시간대를 찾을 수 있습니다. 다음과 같이 주요 기능을 변경하십시오.
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain functiona calls together let closest = tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head printfn "%A" closest
이제 우리가 찾고 있는 시간대가 있어야 합니다.
Timezone Getter를 자체 기능으로 리팩토링하기
이제 나중에 사용할 수 있도록 코드를 자체 기능으로 리팩토링해 보겠습니다. 다음과 같이 함수를 정의합니다.
// the function takes uint as input, and we represent that as "()" let getClosest () = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain function calls together tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head And our main function can just be: [<EntryPoint>] let main argv = printfn "%A" <| getClosest() 0
코드를 실행하면 이전과 동일한 출력이 표시되어야 합니다.

반환 데이터를 인코딩하는 JSON
이제 시간대 데이터를 얻을 수 있으므로 정보를 JSON으로 변환하고 애플리케이션을 통해 제공할 수 있습니다. 이것은 NewtonSoft의 JSON.NET 패키지 덕분에 매우 간단합니다. NuGet 패키지 관리자로 돌아가 Newtonsoft.Json을 찾아 패키지를 설치합니다. 이제 Program.fs로 돌아가서 주요 기능을 약간 변경합니다.
[<EntryPoint>] let main argv = printfn "%s" <| JsonConvert.SerializeObject(getClosest()) 0
지금 코드를 실행하면 TZInfo 개체 대신 JSON이 콘솔에 인쇄되는 것을 볼 수 있습니다.
시간대 정보를 JSON API에 연결
이것을 JSON API에 연결하는 것은 매우 간단합니다. runWebServer 기능을 다음과 같이 변경하기만 하면 됩니다.
// We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ // We are getting the closest time zone, converting it to JSON, then setting the MimeType path "/" >=> request (fun _ -> OK <| JsonConvert.SerializeObject(getClosest())) >=> setMimeType "application/json; charset=utf-8" ] ] // Now we start the server startWebServer cfg app
애플리케이션을 실행하고 localhost:8080으로 이동합니다. 브라우저 창에 JSON이 표시되어야 합니다.
서버 배포
이제 JSON API 서버가 있으므로 인터넷에서 액세스할 수 있도록 배포할 수 있습니다. 이 응용 프로그램을 배포하는 가장 쉬운 방법 중 하나는 관리되는 IIS 서비스로 이해할 수 있는 Microsoft Azure의 App Service를 사용하는 것입니다. Azure App Service에 배포하려면 https://portal.azure.com으로 이동하여 App Service로 이동합니다. 새 앱을 만들고 포털에서 배포 센터로 이동합니다. 포털은 처음 사용하는 경우 다소 압도적일 수 있으므로 문제가 있는 경우 App Service 사용에 대한 많은 자습서 중 하나를 참조하십시오.
다양한 배포 옵션이 표시되어야 합니다. 원하는 것을 사용할 수 있지만 단순성을 위해 FTP 옵션을 사용할 수 있습니다.
App Service는 애플리케이션 실행 방법을 알기 위해 애플리케이션 루트에서 web.config 파일을 찾습니다. 웹 서버는 필수 콘솔 응용 프로그램이므로 HttpPlatformHandler를 사용하여 응용 프로그램을 게시하고 IIS 서버와 통합할 수 있습니다. Visual Studio에서 XML 파일을 프로젝트에 추가하고 이름을 web.config로 지정합니다. 다음 XML로 채우십시오.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <remove name="httpplatformhandler" /> <add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/> </handlers> <httpPlatform stdoutLogEnabled="true" stdoutLogFile="suave.log" startupTimeLimit="20" processPath=".\publish\FivePM.exe" arguments="%HTTP_PLATFORM_PORT%"/> </system.webServer> </configuration>
배포 센터에서 얻은 자격 증명을 사용하여 FTP 서버에 연결합니다(FTP 옵션을 클릭해야 함). web.config를 앱 서비스 FTP 사이트의 wwwroot 폴더로 이동합니다.
이제 우리는 애플리케이션을 빌드하고 게시하고 싶지만 그렇게 하기 전에 서버 코드를 약간 변경해야 합니다. runServer 기능으로 이동하여 처음 세 줄을 다음으로 변경합니다.
let runWebServer (argv:string[]) = // Define the port where you want to serve. We'll hardcode this for now. let port = if argv.Length = 0 then 8080 else (int argv.[0])
이를 통해 애플리케이션은 전달된 인수를 보고 포트를 8080으로 하드 코딩하는 대신 첫 번째 인수를 포트 번호로 사용할 수 있습니다. 웹 구성에서 %HTTP_PLATFORM_PORT%를 첫 번째 인수로 전달하므로 설정해야 합니다.
릴리스 모드에서 애플리케이션을 빌드하고 애플리케이션을 게시한 다음 게시된 폴더를 wwwroot에 복사합니다. 애플리케이션을 다시 시작하면 *.azurewebsites.net
사이트에서 JSON API 결과를 볼 수 있습니다.
이제 애플리케이션이 배포되었습니다!
프런트 엔드
이제 서버가 배포되었으므로 프런트 엔드를 구축할 수 있습니다. 프런트 엔드의 경우 Xamarin 및 F#을 사용하여 Android 애플리케이션을 빌드합니다. 이 스택은 백엔드 환경과 마찬가지로 Visual Studio와의 긴밀한 통합을 즐깁니다. 물론 F# 에코시스템은 상당히 많은 프론트엔드 개발 옵션(WebSharper, Fable/Elmish, Xamarin.iOS, DotLiquid 등)을 지원하지만 간결함을 위해 이 게시물에서는 Xamarin.Android만 사용하여 개발하고 떠납니다. 향후 게시물을 위해.
설정
Android 앱을 설정하려면 새 프로젝트를 시작하고 Xamarin Android 옵션을 선택합니다. Android 개발 도구가 설치되어 있는지 확인합니다. 프로젝트가 설정되면 기본 코드 파일에 다음과 같은 내용이 표시되어야 합니다.
[<Activity (Label = "FivePMFinder", MainLauncher = true, Icon = "@mipmap/icon")>] type MainActivity () = inherit Activity () let mutable count:int = 1 override this.OnCreate (bundle) = base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) button.Click.Add (fun args -> button.Text <- sprintf "%d clicks!" count count <- count + 1 )
F# Android Xamarin의 시작 코드입니다. 코드는 현재 버튼이 클릭된 횟수를 추적하고 현재 카운트 값을 표시합니다. F5 키를 눌러 에뮬레이터를 시작하고 디버그 모드에서 애플리케이션을 시작하면 작동하는 것을 볼 수 있습니다.
UI 구성 요소 추가
몇 가지 UI 구성 요소를 추가하여 더 유용하게 만들어 보겠습니다. 리소스/레이아웃을 열고 Main.axml로 이동합니다.
기본 활동 레이아웃의 시각적 표현이 표시되어야 합니다. 요소를 클릭하여 다양한 UI 요소를 편집할 수 있습니다. 도구 상자로 이동하여 추가하려는 요소를 선택하여 요소를 추가할 수 있습니다. 버튼의 이름을 바꾸고 버튼 아래에 textView를 추가합니다. AXML의 XML 표현은 다음과 유사해야 합니다.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/fivePM" /> <TextView android:text="" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android: /> </LinearLayout>
AXML은 문자열 리소스 파일을 참조하므로 resources/values/strings.xml을 열고 다음과 같이 변경합니다.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="fivePM">It\'s 5PM Somewhere!</string> <string name="app_name">5PM Finder</string> </resources>
이제 프런트 엔드 AXML을 구축했습니다. 이제 일부 코드에 연결해 보겠습니다. MainActivity.fs로 이동하여 onCreate 함수를 다음과 같이 변경합니다.
base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) let txtView = this.FindViewById<TextView>(Resources.Id.textView1); button.Click.Add (fun args -> let webClient = new WebClient() txtView.Text <- webClient.DownloadString("https://fivepm.azurewebsites.net/") )
fivepm.azurewebsites.net을 고유한 JSON API 배포의 URL로 바꿉니다. 응용 프로그램을 실행하고 에뮬레이터에서 버튼을 클릭합니다. 잠시 후 API 결과와 함께 JSON API가 반환되는 것을 볼 수 있습니다.
JSON 파싱
거의 다 왔어! 현재 우리 앱은 원시 JSON을 표시하고 있으며 읽을 수 없습니다. 그런 다음 다음 단계는 JSON을 구문 분석하고 사람이 읽을 수 있는 문자열을 출력하는 것입니다. JSON을 구문 분석하기 위해 서버에서 Newtonsoft.JSON 라이브러리를 사용할 수 있습니다.
NuGet 패키지 관리자로 이동하여 Newtonsoft.JSON을 검색합니다. 설치하고 MainActivity.fs 파일로 돌아갑니다. "open Newtonsoft.Json"을 추가하여 가져옵니다.
이제 프로젝트에 TZInfo 유형을 추가합니다. 서버에서 TZInfo를 재사용할 수 있지만 실제로는 두 개의 필드만 필요하므로 여기에서 사용자 정의 유형을 정의할 수 있습니다.
type TZInfo = { tzName:string localTime: string }
주 함수 위에 유형 정의를 추가하고 이제 주 함수를 다음과 같이 변경합니다.
button.Click.Add (fun args -> let webClient = new WebClient() let tzi = JsonConvert.DeserializeObject<TZInfo>(webClient.DownloadString("https://fivepm.azurewebsites.net/")) txtView.Text <- sprintf "It's (about) 5PM in the\n\n%s Timezone! \n\nSpecifically, it is %s there" tzi.tzName tzi.localTime )
이제 JSON API 결과가 TZInfo 개체로 역직렬화되고 문자열을 구성하는 데 사용됩니다. 앱을 실행하고 버튼을 클릭합니다. 형식이 지정된 문자열이 화면에 표시되어야 합니다.
이 애플리케이션은 매우 간단하고 정제되지 않았지만 F# JSON API를 사용하고 데이터를 변환하여 사용자에게 표시하는 모바일 애플리케이션을 구축했습니다. 그리고 우리는 이 모든 것을 F#으로 했습니다. 다양한 컨트롤을 자유롭게 사용하고 디자인을 개선할 수 있는지 확인하십시오.
그리고 우리는 그것을 가지고 있습니다! 간단한 F# 모바일 애플리케이션과 F# JSON API는 사용자에게 5시 방향을 알려줍니다.
마무리
오늘 우리는 F# 언어의 표현력과 F# 에코시스템의 강점을 모두 시연하면서 F#만 사용하여 간단한 Web API와 간단한 Android 애플리케이션을 구축하는 과정을 살펴보았습니다. 그러나 우리는 F# 개발의 표면을 거의 긁지 않았으므로 오늘 논의한 내용을 기반으로 몇 가지 추가 게시물을 작성하겠습니다. 또한 이 게시물을 통해 자신만의 F# 응용 프로그램을 구축할 수 있기를 바랍니다!
GitHub에서 이 자습서에 사용한 코드를 찾을 수 있습니다.