F# 11의 새로운 기능

F# 11에 오신 것을 환영합니다! 이 릴리스는 도메인 모델을 서로 구분되게 유지하고, 제네릭 알고리즘을 다시 사용하고, 비동기 작업을 조정하고, 컴파일러 메모리 사용을 줄이는 데 도움이 됩니다.

이러한 변경 내용에 대한 제안, 구현, 검토 및 테스트에 대해 F# 커뮤니티에 감사드립니다. 기여자 링크는 아래 항목과 함께 제공됩니다.

Note

예제에는 표시된 기능이 있는 F# 11 컴파일러 및 FSharp.Core 11이 필요합니다. .NET 11.0.100 SDK에서는 F# 11.2 기능에 <LangVersion>preview</LangVersion>가 필요합니다. 언어 설정 및 계획된 SDK 지원을 참조하세요. 런타임 비동기 예제에는 추가 런타임 요구 사항이 있습니다. 이전 .NET 11 SDK만으로는 모든 변경 내용이 포함되지 않을 수 있습니다.

레코드 스프레드: 반복 매핑이 없는 서로 다른 모델

스토리지 모델과 애플리케이션 모델이 명확히 구분되도록, 레코드 스프레드는 공유 필드를 반복해서 선언하지 않고 재사용합니다. 여기서 모델은 고객 세부 정보를 공유하지만 다른 식별자 유형을 사용합니다.

open System

type CustomerId = CustomerId of Guid

type CustomerDetails = { Name: string; PreferredLanguage: string }

type CustomerRow =
    { ...CustomerDetails
      Id: string
      InternalNote: string }

type Customer =
    { ...CustomerDetails
      Id: CustomerId }

...CustomerDetails 는 필드 선언을 추가합니다. ...row는 경계에서 변환이 식별자를 파싱하는 동안 값을 복사합니다.

let toCustomer (row: CustomerRow) =
    match Guid.TryParse row.Id with
    | true, id -> Ok ({ ...row; Id = CustomerId id }: Customer)
    | false, _ -> Error "Invalid customer identifier."

대상은 공유 세부 정보를 유지하고, Id를 대체하고, InternalNote를 제외합니다. Guid.TryParse가 스프레드가 아니라 식별자의 유효성을 검사합니다. 형식이 잘못된 입력은 Error를 생성합니다.

이렇게 하면 애플리케이션 계층 매핑 문제가 해결됩니다. 값 스프레드는 명명된 레코드와 익명 레코드도 결합합니다. 형식 스프레드는 명명된 레코드만 만듭니다. 나중 필드가 우선 적용되고 소스는 순서대로 한 번 실행됩니다. 컴퍼지션 예제와 공유 진화 위험은 레코드 스프레드 연산자를 참조하세요.

레코드 스프레드에 대해 @brianrourkeboll님께 감사드립니다.

제네릭 알고리즘, 연산자 및 튜플에 대한 확장 멤버

기존 형식 간에 알고리즘을 공유하기 위해 F# 11.2를 사용하면 확장 멤버가 정적으로 확인된 SRTP(형식 매개 변수) 제약 조건을 충족할 수 있습니다. 예를 들어 하나의 변환은 래퍼 형식 없이 목록 및 옵션을 처리할 수 있습니다.

Note

.NET 11.0.100 SDK에서 확장이 확인되는 위치의 <LangVersion>preview</LangVersion>를 사용하도록 설정하고 해당 모듈을 엽니다. 라이브러리 요구 사항을 참조하세요.

여러 목록과 옵션에 걸쳐 변환 공유

라이브러리의 map가 범위에 있으면 애플리케이션은 둘 중 어느 표현이든 정리하고, 요소 형식을 변경하고, 변환을 작성할 수 있습니다.

let inline cleanNames values =
    map (fun (name: string) -> name.Trim().ToUpperInvariant()) values

let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]

printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]

새 컨테이너를 Structural.Adapters 부과하는 대신 익숙한 작업을 사용하는 어댑터:

type List<'T> with
    static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
        List.map mapping source

type Option<'T> with
    static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
        Option.map mapping source

공유 함수 Structural.Algorithms는 입력 형식에 정적 Map 멤버를 요구합니다.

let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
    (^Input :
        (static member Map: ^Input * ('T -> 'U) -> ^Output)
        (source, mapping))

^Input 및 ^Output은 정적으로 확인되는 타입입니다. 각 구체적인 호출에서 컴파일러는 필요한 시그니처가 있는 멤버를 선택합니다. 확장은 인터페이스 구현을 추가하지 않습니다. 전체 애플리케이션이 두 모듈을 모두 엽니다.

연산자, 컨텍스트 및 출력 형식

동일한 메커니즘은 튜플 연산자 및 컨텍스트 전파 및 출력 방향 생성을 지원합니다. 네이티브 튜플 헤더는 선언에서뿐만 아니라 확장 해석 호출 위치에서도 F# 11.2 기능을 사용하도록 설정해야 합니다.

별도의 추가 기능으로AllowOverloadOnReturnType, 하나의 팩터리에서 반환값으로 구분되는 오버로드를 선언할 수 있습니다.

type Inject =
    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T list = [value]

    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T option = Some value

let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"

이 Core 11 속성에는 미리보기가 필요하지 않습니다. 반환값 어노테이션은 오버로드를 선택합니다. 없으면 호출은 모호한 상태로 남습니다. 반환 형식별 선택을 참조하세요.

@gusty가 제네릭 프로그래밍 지원을 확장해 준 것에 감사드립니다.

.NET 통합 및 개발 경험

직접 대리자는 처리기의 실제 계약을 유지합니다.

처리기 이름과 바인딩 특성을 프레임워크에 노출하기 위해 F# 11은 래퍼 대신 호환되는 명명된 함수를 직접 대상으로 지정할 수 있습니다. ASP.NET Core 최소 API는 경로, 서비스 및 헤더 정의를 읽을 수 있습니다.

open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection

type ShipmentStore() =
    member _.Status trackingId =
        if trackingId = "SHP-1042" then "Dispatched" else "Unknown"

let status
    (trackingId: string)
    ([<FromServices>] store: ShipmentStore)
    ([<FromHeader(Name = "X-Customer")>] customer: string) =
    $"{customer}: {trackingId} is {store.Status trackingId}"

let handler = Func<string, ShipmentStore, string, string>(status)

실행 가능한 프로젝트 및 생성/호환성 규칙을 참조하세요. 캡처 및 인자 조정에는 래퍼가 여전히 필요할 수 있습니다.

직접 위임에 대해 @kerams님께 감사드립니다.

XML 설명서 재사용

공유 API 비고를 한 곳에서 유지하려면 shared.xml에 배치하세요:

<docs>
  <tracking>
    <remarks>An unrecognized tracking identifier has status Unknown.</remarks>
  </tracking>
</docs>

GenerateDocumentationFile를 사용하도록 설정하면 F# 11에서 <include>이 내보낸 멤버 항목으로 확장됩니다.

/// <summary>Find the latest status of a shipment.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let status trackingId =
    if trackingId = "SHP-1042" then "Dispatched" else "Unknown"

/// <summary>Find the latest statuses of several shipments.</summary>
/// <include file="shared.xml" path="/docs/tracking/*"/>
let statuses trackingIds =
    trackingIds |> List.map (fun id -> id, status id)

include 구현은 한 프래그먼트에서 두 설명을 업데이트합니다. 별도로 도구 해결기는 <inheritdoc/>에 대한 기본 설명을 노출합니다. 컴파일러는 설명서 소비자를 위해 해당 태그를 남깁니다. 예제 및 해결 규칙은 XML 설명서를 참조하세요.

FSharp.Core의 공유 XML 문서를 제공해 준 @T-Gro님께 감사드립니다.

최적화되지 않은 빌드에서 인라인 함수를 디버그합니다

인라인 도우미 인수를 검사하려면 최적화되지 않은 빌드에서 let total에 중단점을 설정하고 addFee 안으로 한 단계씩 실행합니다. 이제 F# 11에서는 이러한 빌드에서는 호출 가능한 메서드를 선호합니다.

let inline addFee (fee: decimal) (amount: decimal) =
    let total = amount + fee
    total

let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42

최적화된 빌드는 인라인화를 유지합니다. 일부 구문은 계속 확장되어야 합니다. 이러한 제한 및 --always-inline 컨트롤에 대해서는 인라인 함수 디버그를 참조하세요.

비동기 및 태스크 조정

F# 워크플로를 태스크 기반 API와 연결하기 위해 FSharp.Core 11은 취소를 인식하는 브리지, 크기가 제한된 배치 처리, 그리고 결과 변환을 추가합니다.

결과 컬렉션뿐만 아니라 미해결 I/O도

대량의 읽기 작업은 애플리케이션이 동시에 진행 중이어야 하는 것보다 더 많은 작업을 시작할 수 있습니다. 작업 기반 작업이 시작되는 시기를 결정하려면 코디네이터는 이미 시작된 작업이 아닌 팩토리가 필요합니다.

open System.IO
open System.Threading

let readFiles (paths: seq<string>) (ct: CancellationToken) =
    paths
    |> Seq.map (fun path ->
        fun token -> File.ReadAllTextAsync(path, token))
    |> Task.parallelLimit 4 ct

최대 4개의 읽기가 동시에 실행됩니다. 결과는 입력 순서를 유지합니다. 실패는 제공된 토큰을 통해 관련 작업의 취소를 요청합니다. 이 경우 제한되는 것은 초당 요청 수가 아니라 동시성입니다. 순차 및 단위 결과 변형은 비동기 및 작업 함수를 참조하세요.

경계를 넘어 취소 및 기저 예외 유지

워크플로가 외부 작업을 시작하면 해당 토큰이 해당 작업과 함께 이동해야 합니다. Async.StartTaskImmediate는 실행 중인 워크플로의 토큰을 태스크 팩터리에 전달하고 완료될 때까지 대기합니다.

open System.IO

let tryReadFile path =
    async {
        try
            let! contents =
                Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
            return Some contents
        with :? FileNotFoundException ->
            return None
    }

단일 작업 실패는 근본 원인인 FileNotFoundException와 함께 이 핸들러에 전달됩니다. 이미 만든 작업 또는 값 태스크의 경우 Async.Await를 사용합니다. 이전 Async.AwaitTask는 예외 래핑 동작을 유지합니다.

작업 기반 호출자의 경우 Task.startAsyncImmediate token workflow는 역방향 브리지를 제공합니다. 첫 번째 비동기 일시 중단까지 호출 스레드에서 시작됩니다.

짧은 변환에는 매칭 도우미를 사용하세요

전체 계산 식 없이 하나의 결과를 변환하려면 Microsoft.FSharp.Control의 해당 헬퍼를 사용합니다. 예를 들어 파일을 읽고, 길이를 가져오고, 일반적인 실패를 다음과 같이 Error 반환합니다:

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Report 1가 포함된 파일은 Ok 8을 제공합니다. 누락된 파일은 FileNotFoundException가 포함된 Error를 제공합니다. 취소는 여전히 전달됩니다. Async 및 ValueTask은 상응하는 map, bind, 및 오류 도우미를 제공합니다. 비동기 및 작업 함수를 참조하세요. 분기나 여러 종속 값에 대해서는 계산 식을 사용합니다.

ValueTask 도우미에는 Core netstandard2.1 또는 net10.0 빌드 및 단일 소비가 필요합니다.

@bartelink의 비동기/작업 API 기여에 감사드립니다.

비동기 실패 보고 후 예외 다시 발생

작동 로깅은 호출자가 catch하는 항목을 변경하거나 오류가 시작된 위치를 숨기지 않고 정보를 추가해야 합니다. 이제 계산 식 처리기는 로거를 대기한 후에도 reraise ()를 사용할 수 있습니다.

Note

계산 식 rethrow는 F# 11.2 기능입니다. .NET 11.0.100 SDK에서 <LangVersion>preview</LangVersion>을(를) 사용하도록 설정합니다.

open System
open System.IO
open System.Threading.Tasks

let readReport (logFailure: exn -> Task) path =
    task {
        try
            return! File.ReadAllTextAsync(path)
        with :? IOException as error ->
            do! logFailure error
            return reraise ()
    }

성공적으로 로깅된 후 동일한 예외 및 원래 throw 사이트 정보가 전파됩니다. 로거 장애는 원본을 대체할 수 있습니다. 이는 런타임 비동기 없이 비동기 가관측성 요청을 해결합니다. 계산 식에서의 Rethrow를 참조하세요.

이 지원에 대한 @xperiandri 덕분에.

FSharp.Core는 최신 .NET에서 빌드할 수 있습니다.

기존 대상에 대한 지원을 중단하지 않고 최신 API를 사용하기 위해 FSharp.Core는 net10.0 및 netstandard2.1와 함께 netstandard2.0를 추가합니다. NuGet은 호환되는 빌드를 선택합니다. 또한 업데이트된 도구 패키징을 통해 컴파일된 프로젝트에서처럼 F# Interactive에서도 작업 기반 비동기 정리가 가능해졌습니다. FSharp.Core 대상 프레임워크를 참조하세요.

@T-Gro 최신 Core 대상 지정 및 SDK 도구 패키징 덕분에.

빌더 작성자용 런타임 비동기

작성기 작성자는 컴파일러에서 생성된 상태 머신을 사용하는 대신 런타임에서 표시된 메서드를 일시 중단하고 다시 시작하도록 할 수 있습니다. 실험적인 라이브러리 정의 빌더는 이 메커니즘을 계산식으로 제공합니다.

let readManifest (path: string) =
    runtimeTask {
        let! text = System.IO.File.ReadAllTextAsync path
        return text.Length
    }

Note

이 실험적 빌더는 인라인 구현이 정의되거나 확장되는 F# 11.2 기능을 사용합니다. .NET 11.0.100 SDK에서는 미리 보기를 사용하도록 설정하세요. 또한 Core 11의 net10.0 빌드와 참조, 그리고 runtime-async 지원이 있는 런타임이 필요합니다. 여기에서 검증된 런타임/참조의 조합은 .NET 11 RC1입니다. .NET 10만 대상으로 하는 것만으로는 충분하지 않습니다. 런타임 비동기 실험을 참조하세요.

FSharp.Core는 이 샘플을 범용 빌더로 기본 제공하지 않습니다. 일반 task 및 async 코드는 그것으로 자동 전환되지 않으며, 이미 컴파일된 일반 작업 반환 API의 호출자는 기본적으로 프리뷰가 필요하지 않습니다.

런타임 비동기에 대해 @majocha님께 감사드립니다.

네이티브 AOT 친화적인 텍스트 및 2차원 배열

네이티브 AOT를 지원하기 위해 F# 11은 보다 일반적으로 사용되는 텍스트 및 배열 관련 작업에서 리플렉션 기반 서식 지정과 동적 할당 요구 사항을 제거합니다.

이미 문자열이 있으면, 그것을 쓰기 위해 굳이 "%s" 형식이 필요하지 않습니다. 새로운 Core 함수 string 및 printn는 F#의 print 변환을 통해 값을 받으며, printn는 줄 바꿈 문자를 추가합니다:

let report (processed: int) (total: decimal) (elapsed: TimeSpan) : string =
    $"{processed} items, total {total:F2}, elapsed {elapsed.TotalMilliseconds:F0} ms"

printn (report 12 145.50m (TimeSpan.FromMilliseconds 250.0))
// 12 items, total 145.50, elapsed 250 ms

문자열 보간은 직접 변환을 사용하고, 더 많은 경우에 String.Concat를 사용합니다. %A와 같은 .NET 형식 자리 표시자는 고정 문화권 서식을 사용하고 값을 박싱합니다. {total:F2} 및 정밀도 지정이 있는 printf 형식은 여전히 printf 서식이 필요할 수 있습니다. 지역화된 출력에 대한 명시적인 문화권(로캘)을 선택합니다.

--reflectionfree를 사용하면 레코드 및 유니온도 생성된 ToString 출력을 얻습니다. 문화 예제 및 지원되는 경로는 보간 및 네이티브 AOT를 참조하세요.

@bbatsov에게는 출력 함수를, @charlesroddie에게는 보간 구현을 해 준 것에 감사드립니다.

일반 0부터 시작하는 그리드 작업 유지

0부터 시작하는 그리드는 동적 코드 할당 경로를 피하려고 Array2D를 대체하는 모듈까지 필요해서는 안 됩니다. 업데이트된 핵심 구현은 익숙한 방식의 초기화 및 변환을 지원합니다.

let grid = Array2D.init 3 4 (fun row column -> row * 4 + column)
let doubled = grid |> Array2D.map (fun value -> value * 2)
let snapshot = Array2D.copy doubled

printn $"{snapshot.GetLength(0)} x {snapshot.GetLength(1)}; last = {snapshot[2, 3]}"
// 3 x 4; last = 22

Array2D.create, init및 rebase Core 빌드에서 0부터 시작하는 할당 경로를 사용합니다. 경계 보존 map, mapi 및 copy는 net10.0 빌드에서 더 새로운 .NET 할당 API를 사용할 수 있습니다. .NET Standard 빌드는 대체 방식을 유지합니다. 0부터 시작하지 않는 배열은 더 이상 사용되지 않는 것은 아니지만 네이티브 할당 제한은 그대로 유지됩니다.

2차원 배열 및 네이티브 AOT의 할당 경로와 남아 있는 제한 사항을 참조하세요. 전체 애플리케이션의 게시 진단 정보를 확인하세요.

@T-Gro의 Array2D 개선에 감사드립니다.

메모리 사용량 감소 및 작업 반복 감소

F# 11은 반복되는 컴파일러 분석 및 일부 애플리케이션 메모리 할당을 줄입니다. 이는 다양한 워크로드에 영향을 줍니다.

여러 프로젝트에서 참조된 라이브러리 공유하기

공유 이진 종속성의 반복 복사본을 방지하기 위해 언어 서비스는 프로젝트 간에 적격 가져온 라이브러리 그래프를 공유할 수 있습니다.

구현 PR의 유지 메모리 실험에서 10개 프로젝트 ReSharper.FSharp 솔루션은 공유를 사용하도록 설정했을 때 1,547.2MB에서 1,002.2MB로 감소했습니다. 이는 F# 10에서 11로의 일반적인 감소가 아니라 해당 워크로드에 대한 비활성/활성 기준 측정값입니다. 공유 이진 참조가 아닌 프로젝트 참조가 지배하는 솔루션은 훨씬 더 작은 이익을 볼 수 있습니다.

메타데이터 읽기 및 인라인 특수화는 임시 할당이라는 별도의 메트릭도 줄입니다. 범위 및 컨트롤에 대한 도구 성능을 참조하세요.

공유 라이브러리 가져오기를 지원해 준 @auduchinok님께 감사드립니다.

기본적으로 독립적인 작업을 병렬로 컴파일합니다

이제 결정적 출력 및 소스 순서 규칙이 유지되는 병렬 컴파일이 기본적으로 사용하도록 설정됩니다. 오버로드 캐싱 및 보호된 패턴 개선은 다른 반복 작업을 방지합니다. 또한 Visual Studio/FCS는 변경되지 않은 문서 분석 및 바인딩된 동시 검사를 줄입니다. 도구 성능을 참조하세요.

콜백과 빈 결과를 위한 할당으로 인한 오버헤드 방지

선택된 List 및 Array 함수는 이제 콜백을 인라인으로 처리합니다. 사용자 정의 인라인 도우미의 경우, OptimizeClosureIfNotInlined는 불투명한 커리된 콜백을 한 번 변환합니다:

let inline foldItems
    ([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
    state
    (items: 'T[]) =
    let mutable total = state
    for item in items do
        total <- folder total item
    total

다른 할당은 유지될 수 있습니다. 요구 사항은 커링된 콜백 최적화를 참조하세요.

빈 결과는 성공한 작업의 또 다른 일반적인 사례입니다. FSharpLint의 개발 계기가 된 워크로드는아무 경고도 찾지 못한 규칙들을 반복적으로 조합했습니다. 이제 Core는 더 많은 작업에서 공유되는 빈 배열을 재사용합니다.

let checkTitle (title: string) =
    if String.IsNullOrWhiteSpace title then [|"A title is required."|]
    else Array.empty

let warnings = ["Introduction"; "Installation"] |> List.toArray |> Array.collect checkTitle
let sharedEmpty = obj.ReferenceEquals(warnings, Array.empty<string>)
// true: no new empty result array

이 변경으로 파이프라인의 모든 할당이 아니라 중복된 빈 배열 개체가 제거됩니다. 서로 다른 참조 동일성을 갖는 별도의 빈 결과에 의존하지 마세요.

@T-Gro에게 인라인 컬렉션 콜백과 공유 빈 배열에 감사드립니다.

보다 정확한 API 호출 및 일상적인 편의성

생성된 매개변수 순서가 변경될 수 있는 경우 이름을 필수로 지정

생성된 API를 명시적 알림 없이 발생하는 매개변수 순서 변경으로부터 보호하려면 작성자가 명명된 인수를 요구할 수 있습니다.

Note

강제 적용은 F# 11.2 기능입니다. .NET 11.0.100 SDK에서 미리 보기를 사용하도록 설정하세요. 사용 가능한 System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute 정의도 필요합니다. 전체 샘플은 호환성 정의가 없는 참조에 대한 호환성 정의를 제공합니다.

open System.Diagnostics.CodeAnalysis

type Query =
    [<RequireNamedArguments>]
    static member Execute(numerator: decimal, denominator: decimal) =
        numerator / denominator

let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.

선언 순서를 다시 지정하면 명명된 호출의 결과가 0.75M유지되고 위치 호출이 거부됩니다. 생성된 API 사용 사례의 경우 공급자가 주석을 내보내야 합니다.

이미 래핑된 제네릭 인수 인식

기존 Task를 일반 결과 값으로 래핑하지 않으려면 새 최후의 수단 오버로드 규칙은 bare Task<'T>보다 'T를 우선합니다.

Note

가장 구체적인 타이브레이커 및 오버로드 우선순위 동작은 F# 11.2의 기능이며, .NET 11.0.100 SDK에서는 미리 보기 기능을 사용하도록 설정하세요.

open System.Threading.Tasks

let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>

결과는 ValueTask<Task<int>>가 아니라 ValueTask<int>입니다. 규칙의 제한에 대한 Overload selection을 참조하세요.

이전 진입점을 제거하지 않고 새 오버로드를 선호합니다.

라이브러리는 호출자의 소스 위치를 포함하는 오버로드를 추가하는 동안 기존 이진 파일에 대한 이전 진단 방법을 유지할 수 있습니다. OverloadResolutionPriorityAttribute는 새로 컴파일된 호출자에게는 그 기본 설정을 명시적으로 지정합니다.

open System.Runtime.CompilerServices

type Check =
    static member That(condition: bool) =
        if condition then "OK" else "Check failed."

    [<OverloadResolutionPriority(1)>]
    static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
        if condition then "OK" else $"Check failed at line {defaultArg line 0}."

let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.

변경되지 않은 호출은 줄 인식 오버로드를 선택합니다. 범위 및 호환성 규칙에 대한 오버로드 선택을 참조하세요.

.NET의 조건부 null 허용성 보장 사용

파일 경로가 null이 아닌 경우 경로 API의 null 허용 결과가 자체 계약상 배제되는 검사를 강요해서는 안 됩니다. F# 11은 기존 .NET NotNullIfNotNull 프라미스를 사용합니다. null이 아닌 명명된 입력은 null이 아닌 결과를 의미합니다.

open System.IO

let fileName: string = Path.GetFileName("reports/annual.csv")
let extension: string = Path.GetExtension(fileName)
printn (extension.ToUpperInvariant())
// .CSV

let fileNameOf (path: string | null) : string | null =
    Path.GetFileName path

<Nullable>enable</Nullable>를 활성화하다 nullable 입력은 여전히 nullable 처리가 필요합니다; Conditional return contracts를 참조하세요. 제약이 없는 C# T? API의 경우 별도의 Unchecked.withNull 도우미를 사용합니다.

@kerams 덕분에 조건부 null 허용 여부를 지원하게 되었습니다.

더 작은 언어 개선 사항

레코드 형식을 .NET 생성자 구문으로 만들려면 F# 11.2 레코드 생성자를 사용합니다.

type Person = { Name: string; Age: int }
let person = Person(Name = "Ada", Age = 36)

명명된 인수는 필드 순서에 의존하지 않습니다. @charlesroddie님 덕분에 이 기능이 가능했습니다.

조건부 소스의 경우 #elif는 중첩된 #if 그룹을 방지합니다.

#if EU_WAREHOUSE
let dispatchTimeZone = "Europe/Paris"
#elif US_WAREHOUSE
let dispatchTimeZone = "America/New_York"
#else
let dispatchTimeZone = "UTC"
#endif

System.Console.WriteLine(dispatchTimeZone)

첫 번째로 일치하는 분기가 적용됩니다. 샘플 프로젝트는 EU_WAREHOUSE를 정의하고 Europe/Paris를 출력합니다. 두 기호가 모두 정의되지 않으면 출력은 UTC입니다.

관련 도구 및 interop 사례의 경우:

업그레이드를 하기 전에

업그레이드할 때 다음과 같은 결과적 변경 사항에 유의하세요.

영향을 받는 코드 Action
파일이 있는 .fsi 라이브러리 적용된 계약 특성을 서명에 복사합니다. 누락은 이제 FS3888 오류입니다.
확장 기반 인라인 라이브러리 유추된 서명 및 생산자/소비자 요구 사항을 검토합니다. F# 11.2의 형식 유추는 보다 일반적인 제약 조건을 유지할 수 있습니다.
인스턴스 메서드 대리자 이제 null 수신기 오류가 발생하므로 직접 생성 전에 수신기의 유효성을 검사합니다.
지역화된 보간 출력 .NET 형식 지정 자리 표시자는 불변 문화권을 사용합니다. 지역화된 출력이 필요한 경우 명시적으로 지정된 문화권 정보를 선택합니다.
final이 있는 사용자 지정 빌더 do! 바인딩 및 단위 완성을 지원합니다. final do!은 더 이상 최종 전달 멤버를 선택하지 않습니다.
미완성 let! 다음 계산 본문을 추가합니다. 이제 불완전한 바인딩에 대해 더 직접적인 진단 메시지가 제공됩니다.
기존 언어 설정 지원되는 번호 매기기 모드는 F# 8에서 시작합니다. 비 엄격한 들여쓰기 모드가 제거됩니다. 언어 버전 및 들여쓰기를 참조하세요.
열거형 비트 단위 연산 정수 기반 열거형을 사용합니다. 잘못된 기반 형식은 거부됩니다.