Есть ли способ в F # написать обобщенную функцию c, которая будет принимать значение Tuple или Single? - PullRequest
4 голосов
/ 01 апреля 2020

Я пишу код для обработки большого файла данных. CSV TypeProvider вернет NaN для данного значения, если поле равно none. У меня довольно много полей, так что будет лучше, если я напишу вспомогательную функцию для проверки этого NaN и вместо этого верну None. Не зная, как написать более общую вспомогательную функцию c, я придумал следующее:

let (!?) x = if Double.IsNaN(x) then None else Some (decimal x)
let (!?) (x, y) = 
    match (x, y) with
    | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
    | (_, _) -> None

К сожалению, моя попытка перегрузки оператора не работает должным образом, и дублирование кода также невелико. Новичок ie спрашивает, есть ли лучший способ сделать это?

(я знаю что-то вроде PreferOptionals, но мне нужно сделать это более избирательно)

Ответы [ 2 ]

5 голосов
/ 01 апреля 2020

Привязка F # имеет одно определение. Повторное его определение просто затеняет предыдущее определение.

let f x = x
let f x = x * x //previous f is now shadowed

Вот способ использования статически разрешенных типов для достижения того же результата. Мы определяем элементы в промежуточном типе (DU с одним регистром), который называется Converter:

type Converter = Converter with
    static member ToOption (_ : Converter, value) = 
        if Double.IsNaN(value) then None else Some(value)

    static member ToOption (_ : Converter, (x, y)) = 
        if Double.IsNaN(x) || Double.IsNaN(y) then None else Some(x, y)

let inline (!?) (x : ^a) =
    ((^b or ^a) : (static member ToOption : ^b * ^a -> _) (Converter, x))
5 голосов
/ 01 апреля 2020

Вы должны сделать их stati c членами промежуточного типа:

open System

type T = T with
    static member ($) (T, x) = if Double.IsNaN(x) then None else Some (decimal x)
    static member ($) (T,(x, y)) = 
        match (x, y) with
        | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
        | (_, _) -> None

let inline parse x = T $ x


// Usage

let a = parse (1. , 2.)
// val a : (decimal * decimal) option = Some (1M, 2M)

let b = parse 1.
// val b : decimal option = Some 1M

Также обратите внимание, что лучше использовать бинарный оператор для отправки также промежуточного типа. Таким образом, вы задерживаете разрешение перегрузки.

Вы также можете использовать именованные функции, но это более многословно.

EDIT Относительно задержки разрешения перегрузки.

First of все, я сказал, чтобы написать это как именованную функцию, вы должны написать ограничения вручную:

let inline parse (x: ^a) =
    ((^b or ^a) : (static member ($) : ^b -> ^a -> _) T,x)

Обратите внимание, что другой ответ, который был добавлен позже, точно такой же, как этот, единственное отличие является то, что он использует имя ToOption для члена stati c вместо оператора.

Хорошо, теперь давайте попробуем избавиться от T, мы можем сделать это до некоторой степени:

type T = T with
    static member ($) x = if Double.IsNaN(x) then None else Some (decimal x)
    static member ($) ((x, y)) = 
        match (x, y) with
        | (x, y) when not(Double.IsNaN(x)) && not (Double.IsNaN(y))  -> Some (decimal x, decimal y)
        | (_, _) -> None

let inline parse (x: ^a) =
    let call (_:'b) = ((^b or ^a) : (static member ($) : ^a -> _) x)
    call T

Обратите внимание, что мне пришлось создать способ объединения ^b с T, поэтому я добавил функцию call.

Теперь это все еще работает, и это правильное решение который очищает сигнатуру перегрузки и добавляет немного шума в функцию анализа, что во многих сценариях ios является хорошим компромиссом. Но что, если я полностью удаляю параметр ^a из вызова черты?

let inline parse (x: ^a) =
    let call (_:'b) = (^b : (static member ($) : ^a -> _) x)
    call T

это не получается с

~vs6086.fsx(11,27): error FS0043: A unique overload for method 'op_Dollar' could not be determined based on type information prior to this program point. A type annotation may be needed.

Known return type: 'b

Known type parameter: <  ^a >

Candidates:
 - static member T.( $ ) : (float * float) -> (decimal * decimal) option
 - static member T.( $ ) : x:float -> decimal option

Почему это так?

Потому что теперь Компилятор F # знает на сайте trait-call все задействованные переменные типа.

До этого последнего изменения ^a было неизвестно, а ^b было объединено с T, но, поскольку другое было неизвестно, перегрузка разрешение было отложено до каждого последующего отдельного вызова функции parse, что выполнимо, поскольку она объявлена ​​встроенной.

Зная все параметры типа, он пытается применить стандартное разрешение перегрузки Net и завершается неудачно, поскольку 2 кандидата.

Надеюсь, это объяснение имеет смысл.

...