F# 5의 새로운 기능
F# 5는 F# 언어 및 F# 대화형에 몇 가지 개선 사항을 추가합니다. .NET 5와 함께 릴리스됩니다.
.NET 다운로드 페이지에서 최신 .NET SDK를 다운로드할 수 있습니다.
시작하기
F# 5는 모든 .NET Core 배포 및 Visual Studio 도구에서 사용할 수 있습니다. 자세한 내용은 F# 으로 시작하기를 참조하세요.
F# 스크립트의 패키지 참조
F# 5는 구문을 사용하여 F# 스크립트에서 패키지 참조를 지원합니다 #r "nuget:..."
. 예를 들어 다음 패키지 참조를 고려합니다.
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
let o = {| X = 2; Y = "Hello" |}
printfn $"{JsonConvert.SerializeObject o}"
다음과 같이 패키지 이름 뒤의 명시적 버전을 제공할 수도 있습니다.
#r "nuget: Newtonsoft.Json,11.0.1"
패키지 참조는 ML.NET 같은 네이티브 종속성이 있는 패키지를 지원합니다.
또한 패키지 참조는 종속 .dll
s 참조에 대한 특별한 요구 사항이 있는 패키지를 지원합니다. 예를 들어 FParsec 패키지는 사용자가 F# Interactive에서 참조되기 전에 FParsec.dll
해당 종속 FParsecCS.dll
성이 먼저 참조되었는지 수동으로 확인하도록 요구하는 데 사용됩니다. 더 이상 필요하지 않으며 다음과 같이 패키지를 참조할 수 있습니다.
#r "nuget: FParsec"
open FParsec
let test p str =
match run p str with
| Success(result, _, _) -> printfn $"Success: {result}"
| Failure(errorMsg, _, _) -> printfn $"Failure: {errorMsg}"
test pfloat "1.234"
이 기능은 F# 도구 RFC FST-1027을 구현합니다. 패키지 참조에 대한 자세한 내용은 F# 대화형 자습서를 참조하세요.
문자열 보간
F# 보간된 문자열은 C# 또는 JavaScript 보간된 문자열과 매우 유사합니다. 즉, 문자열 리터럴 내의 "구멍"에 코드를 작성할 수 있습니다. 기본 예제는 다음과 같습니다.
let name = "Phillip"
let age = 29
printfn $"Name: {name}, Age: {age}"
printfn $"I think {3.0 + 0.14} is close to {System.Math.PI}!"
그러나 F# 보간된 문자열은 함수와 마찬가지로 sprintf
형식화된 보간을 허용하여 보간된 컨텍스트 내의 식이 특정 형식을 준수하도록 합니다. 동일한 형식 지정자를 사용합니다.
let name = "Phillip"
let age = 29
printfn $"Name: %s{name}, Age: %d{age}"
// Error: type mismatch
printfn $"Name: %s{age}, Age: %d{name}"
위의 형식화된 보간 예제 %s
에서는 보간이 형식 string
이어야 하는 반면 %d
보간은 형식이어야 integer
합니다.
또한 임의의 F# 식(또는 식)을 보간 컨텍스트의 측면에 배치할 수 있습니다. 다음과 같이 더 복잡한 식을 작성할 수도 있습니다.
let str =
$"""The result of squaring each odd item in {[1..10]} is:
{
let square x = x * x
let isOdd x = x % 2 <> 0
let oddSquares xs =
xs
|> List.filter isOdd
|> List.map square
oddSquares [1..10]
}
"""
실제로는 이 작업을 너무 많이 수행하지 않는 것이 좋습니다.
이 기능은 F# RFC FS-1001을 구현합니다.
nameof에 대한 지원
F# 5는 nameof
사용 중인 기호를 확인하고 F# 원본에서 해당 이름을 생성하는 연산자를 지원합니다. 이는 로깅과 같은 다양한 시나리오에서 유용하며 소스 코드의 변경으로부터 로깅을 보호합니다.
let months =
[
"January"; "February"; "March"; "April";
"May"; "June"; "July"; "August"; "September";
"October"; "November"; "December"
]
let lookupMonth month =
if (month > 12 || month < 1) then
invalidArg (nameof month) (sprintf "Value passed in was %d." month)
months[month-1]
printfn $"{lookupMonth 12}"
printfn $"{lookupMonth 1}"
printfn $"{lookupMonth 13}"
마지막 줄에 예외가 발생하며 오류 메시지에 "month"가 표시됩니다.
거의 모든 F# 구문의 이름을 사용할 수 있습니다.
module M =
let f x = nameof x
printfn $"{M.f 12}"
printfn $"{nameof M}"
printfn $"{nameof M.f}"
세 가지 최종 추가 사항은 연산자의 작동 방식에 대한 변경 사항입니다. 제네릭 형식 매개 변수에 대한 폼 추가 nameof<'type-parameter>
및 패턴 일치 식에서 패턴으로 사용할 nameof
수 있는 기능입니다.
연산자의 이름을 지정하면 원본 문자열이 지정됩니다. 컴파일된 양식이 필요한 경우 연산자의 컴파일된 이름을 사용합니다.
nameof(+) // "+"
nameof op_Addition // "op_Addition"
형식 매개 변수의 이름을 사용하려면 약간 다른 구문이 필요합니다.
type C<'TType> =
member _.TypeName = nameof<'TType>
이는 연산자 및 연산자와 typeof<'T>
typedefof<'T>
비슷합니다.
F# 5에서는 식에 사용할 수 있는 패턴에 match
대한 nameof
지원도 추가합니다.
[<Struct; IsByRefLike>]
type RecordedEvent = { EventType: string; Data: ReadOnlySpan<byte> }
type MyEvent =
| AData of int
| BData of string
let deserialize (e: RecordedEvent) : MyEvent =
match e.EventType with
| nameof AData -> AData (JsonSerializer.Deserialize<int> e.Data)
| nameof BData -> BData (JsonSerializer.Deserialize<string> e.Data)
| t -> failwithf "Invalid EventType: %s" t
앞의 코드는 일치 식에서 문자열 리터럴 대신 'nameof'를 사용합니다.
이 기능은 F# RFC FS-1003을 구현합니다.
형식 선언 열기
또한 F# 5는 개방형 형식 선언에 대한 지원을 추가합니다. 개방형 형식 선언은 C#에서 정적 클래스를 여는 것과 같습니다. 단, F# 의미 체계에 맞게 약간 다른 구문과 약간 다른 동작이 있습니다.
열려 있는 형식 선언을 사용하면 모든 형식을 사용하여 내부 정적 콘텐츠를 노출할 수 있습니다 open
. 또한 F#정의 공용 구조체 및 레코드를 사용하여 콘텐츠를 노출할 수 open
있습니다. 예를 들어 모듈에 공용 구조체가 정의되어 있고 해당 사례에 액세스하려고 하지만 전체 모듈을 열지 않으려는 경우에 유용할 수 있습니다.
open type System.Math
let x = Min(1.0, 2.0)
module M =
type DU = A | B | C
let someOtherFunction x = x + 1
// Open only the type inside the module
open type M.DU
printfn $"{A}"
C#과 달리 이름이 같은 멤버를 노출하는 두 형식의 경우 open type
ed인 open
마지막 형식의 멤버가 다른 이름을 숨깁니다. 이는 이미 존재하는 그림자에 대한 F# 의미 체계와 일치합니다.
이 기능은 F# RFC FS-1068을 구현합니다.
기본 제공 데이터 형식에 대한 일관된 조각화 동작
F# 5 이전의 일관성이 없는 기본 제공 FSharp.Core
데이터 형식(배열, 목록, 문자열, 2D 배열, 3D 배열, 4D 배열)을 조각화하기 위한 동작입니다. 일부 에지 대/소문자 동작은 예외를 throw했고 일부는 예외를 throw하지 않았습니다. 이제 F# 5에서 모든 기본 제공 형식은 생성할 수 없는 조각에 대해 빈 조각을 반환합니다.
let l = [ 1..10 ]
let a = [| 1..10 |]
let s = "hello!"
// Before: would return empty list
// F# 5: same
let emptyList = l[-2..(-1)]
// Before: would throw exception
// F# 5: returns empty array
let emptyArray = a[-2..(-1)]
// Before: would throw exception
// F# 5: returns empty string
let emptyString = s[-2..(-1)]
이 기능은 F# RFC FS-1077을 구현합니다.
FSharp.Core의 3D 및 4D 배열에 대한 고정 인덱스 조각
F# 5는 기본 제공 3D 및 4D 배열 형식에서 고정 인덱스로 조각화할 수 있습니다.
이를 설명하기 위해 다음 3D 배열을 고려합니다.
z = 0
x\y | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 2 | 3 |
z = 1
x\y | 0 | 1 |
---|---|---|
0 | 4 | 5 |
1 | 6 | 7 |
배열에서 조각을 [| 4; 5 |]
추출하려면 어떻게 해야 할까요? 이것은 이제 매우 간단합니다!
// First, create a 3D array to slice
let dim = 2
let m = Array3D.zeroCreate<int> dim dim dim
let mutable count = 0
for z in 0..dim-1 do
for y in 0..dim-1 do
for x in 0..dim-1 do
m[x,y,z] <- count
count <- count + 1
// Now let's get the [4;5] slice!
m[*, 0, 1]
이 기능은 F# RFC FS-1077b를 구현 합니다.
F# 따옴표 개선 사항
이제 F# 코드 따옴표에 형식 제약 조건 정보를 유지할 수 있습니다. 다음 예제를 참조하세요.
open FSharp.Linq.RuntimeHelpers
let eval q = LeafExpressionConverter.EvaluateQuotation q
let inline negate x = -x
// val inline negate: x: ^a -> ^a when ^a : (static member ( ~- ) : ^a -> ^a)
<@ negate 1.0 @> |> eval
함수에서 inline
생성된 제약 조건은 코드 따옴표로 유지됩니다. negate
이제 함수의 따옴표 붙은 폼을 평가할 수 있습니다.
이 기능은 F# RFC FS-1071을 구현합니다.
적용 계산 식
CES(계산 식) 는 오늘날 "상황별 계산"을 모델링하거나 더 기능적인 프로그래밍 친화적인 용어인 모나딕 계산에서 사용됩니다.
F# 5에는 다른 계산 모델을 제공하는 적용 CE가 도입되었습니다. 적용 CES를 사용하면 모든 계산이 독립적이며 결과가 마지막에 누적되는 경우 보다 효율적인 계산을 할 수 있습니다. 계산이 서로 독립적이면 쉽게 병렬 처리할 수 있으므로 CE 작성자가 보다 효율적인 라이브러리를 작성할 수 있습니다. 그러나 이 이점은 이전에 계산된 값에 의존하는 계산은 허용되지 않는다는 제한 사항이 있습니다.
다음 예제에서는 형식에 대한 기본 적용 CE를 Result
보여줍니다.
// First, define a 'zip' function
module Result =
let zip x1 x2 =
match x1,x2 with
| Ok x1res, Ok x2res -> Ok (x1res, x2res)
| Error e, _ -> Error e
| _, Error e -> Error e
// Next, define a builder with 'MergeSources' and 'BindReturn'
type ResultBuilder() =
member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x
let result = ResultBuilder()
let run r1 r2 r3 =
// And here is our applicative!
let res1: Result<int, string> =
result {
let! a = r1
and! b = r2
and! c = r3
return a + b - c
}
match res1 with
| Ok x -> printfn $"{nameof res1} is: %d{x}"
| Error e -> printfn $"{nameof res1} is: {e}"
let printApplicatives () =
let r1 = Ok 2
let r2 = Ok 3 // Error "fail!"
let r3 = Ok 4
run r1 r2 r3
run r1 (Error "failure!") r3
현재 라이브러리에서 CE를 노출하는 라이브러리 작성자인 경우 몇 가지 추가 고려 사항을 알아야 합니다.
이 기능은 F# RFC FS-1063을 구현합니다.
인터페이스는 다른 제네릭 인스턴스화에서 구현할 수 있습니다.
이제 다른 제네릭 인스턴스화에서 동일한 인터페이스를 구현할 수 있습니다.
type IA<'T> =
abstract member Get : unit -> 'T
type MyClass() =
interface IA<int> with
member x.Get() = 1
interface IA<string> with
member x.Get() = "hello"
let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>
iaInt.Get() // 1
iaString.Get() // "hello"
이 기능은 F# RFC FS-1031을 구현합니다.
기본 인터페이스 멤버 사용
F# 5를 사용하면 기본 구현을 사용하는 인터페이스를 사용할 수 있습니다.
다음과 같이 C#에 정의된 인터페이스를 고려합니다.
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
인터페이스를 구현하는 표준 방법을 통해 F#에서 사용할 수 있습니다.
open CSharp
// You can implement the interface via a class
type MyType() =
member _.M() = ()
interface MyDim
let md = MyType() :> MyDim
printfn $"DIM from C#: %d{md.Z}"
// You can also implement it via an object expression
let md' = { new MyDim }
printfn $"DIM from C# but via Object Expression: %d{md'.Z}"
이렇게 하면 사용자가 기본 구현을 사용할 수 있을 것으로 예상할 때 최신 C#으로 작성된 C# 코드 및 .NET 구성 요소를 안전하게 활용할 수 있습니다.
이 기능은 F# RFC FS-1074를 구현합니다.
nullable 값 형식을 사용하여 간소화된 interop
Nullable(값) 형식(지금까지 Nullable 형식이라고 함)은 F#에서 오랫동안 지원해 왔지만 값을 전달할 때마다 래퍼를 Nullable<SomeType>
생성 Nullable
해야 하므로 일반적으로 이러한 형식과 상호 작용하는 것은 다소 고통스러웠습니다. 이제 컴파일러는 대상 형식이 일치하는 경우 값 형식을 Nullable<ThatValueType>
암시적으로 변환합니다. 이제 다음 코드가 가능합니다.
#r "nuget: Microsoft.Data.Analysis"
open Microsoft.Data.Analysis
let dateTimes = PrimitiveDataFrameColumn<DateTime>("DateTimes")
// The following line used to fail to compile
dateTimes.Append(DateTime.Parse("2019/01/01"))
// The previous line is now equivalent to this line
dateTimes.Append(Nullable<DateTime>(DateTime.Parse("2019/01/01")))
이 기능은 F# RFC FS-1075를 구현합니다.
미리 보기: 역방향 인덱스
또한 F# 5에서는 역방향 인덱스를 허용하기 위한 미리 보기가 도입되었습니다. 구문은 ^idx
입니다. 목록의 끝에서 요소 1 값을 만드는 방법은 다음과 같습니다.
let xs = [1..10]
// Get element 1 from the end:
xs[^1]
// From the end slices
let lastTwoOldStyle = xs[(xs.Length-2)..]
let lastTwoNewStyle = xs[^1..]
lastTwoOldStyle = lastTwoNewStyle // true
고유한 형식에 대한 역방향 인덱스를 정의할 수도 있습니다. 이렇게 하려면 다음 방법을 구현해야 합니다.
GetReverseIndex: dimension: int -> offset: int
다음은 형식의 예입니다.Span<'T>
open System
type Span<'T> with
member sp.GetSlice(startIdx, endIdx) =
let s = defaultArg startIdx 0
let e = defaultArg endIdx sp.Length
sp.Slice(s, e - s)
member sp.GetReverseIndex(_, offset: int) =
sp.Length - offset
let printSpan (sp: Span<int>) =
let arr = sp.ToArray()
printfn $"{arr}"
let run () =
let sp = [| 1; 2; 3; 4; 5 |].AsSpan()
// Pre-# 5.0 slicing on a Span<'T>
printSpan sp[0..] // [|1; 2; 3; 4; 5|]
printSpan sp[..3] // [|1; 2; 3|]
printSpan sp[1..3] // |2; 3|]
// Same slices, but only using from-the-end index
printSpan sp[..^0] // [|1; 2; 3; 4; 5|]
printSpan sp[..^2] // [|1; 2; 3|]
printSpan sp[^4..^2] // [|2; 3|]
run() // Prints the same thing twice
이 기능은 F# RFC FS-1076을 구현합니다.
미리 보기: 계산 식에서 사용자 지정 키워드(keyword) 오버로드
계산 식은 라이브러리 및 프레임워크 작성자에게 강력한 기능입니다. 이를 통해 잘 알려진 멤버를 정의하고 작업 중인 기본 DSL을 형성하여 구성 요소의 표현력을 크게 향상시킬 수 있습니다.
F# 5에서는 계산 식에서 사용자 지정 작업을 오버로드하기 위한 미리 보기 지원을 추가합니다. 다음 코드를 작성하고 사용할 수 있습니다.
open System
type InputKind =
| Text of placeholder:string option
| Password of placeholder: string option
type InputOptions =
{ Label: string option
Kind : InputKind
Validators : (string -> bool) array }
type InputBuilder() =
member t.Yield(_) =
{ Label = None
Kind = Text None
Validators = [||] }
[<CustomOperation("text")>]
member this.Text(io, ?placeholder) =
{ io with Kind = Text placeholder }
[<CustomOperation("password")>]
member this.Password(io, ?placeholder) =
{ io with Kind = Password placeholder }
[<CustomOperation("label")>]
member this.Label(io, label) =
{ io with Label = Some label }
[<CustomOperation("with_validators")>]
member this.Validators(io, [<ParamArray>] validators) =
{ io with Validators = validators }
let input = InputBuilder()
let name =
input {
label "Name"
text
with_validators
(String.IsNullOrWhiteSpace >> not)
}
let email =
input {
label "Email"
text "Your email"
with_validators
(String.IsNullOrWhiteSpace >> not)
(fun s -> s.Contains "@")
}
let password =
input {
label "Password"
password "Must contains at least 6 characters, one number and one uppercase"
with_validators
(String.exists Char.IsUpper)
(String.exists Char.IsDigit)
(fun s -> s.Length >= 6)
}
이 변경 전에 형식을 InputBuilder
있는 그대로 작성할 수 있지만 예제에서 사용되는 방식으로는 사용할 수 없습니다. 오버로드, 선택적 매개 변수 및 이제 System.ParamArray
형식이 허용되므로 모든 것이 예상대로 작동합니다.
이 기능은 F# RFC FS-1056을 구현합니다.
.NET