Domanda Funziona con tipi di parametri generici


Sto cercando di capire come definire una funzione che funzioni su più tipi di parametri (ad es. Int e int64). A quanto ho capito, l'overloading delle funzioni non è possibile in F # (certamente il compilatore si lamenta). Prendi ad esempio la seguente funzione.

let sqrt_int = function
    | n:int   -> int (sqrt (float n))
    | n:int64 -> int64 (sqrt (float n))

Il compilatore ovviamente lamenta che la sintassi non è valida (sembra che i vincoli di tipo nella corrispondenza dei pattern non siano supportati sembra), anche se penso che questo illustri cosa mi piacerebbe ottenere: una funzione che opera su diversi tipi di parametri e restituisce un valore di genere. Ho la sensazione che questo sia possibile in F # usando una combinazione di tipi generici / tipo di inferenza / pattern matching, ma la sintassi mi ha eluso. Ho anche provato a usare il:? operatore (test di tipo dinamico) e quando clausole nel blocco di corrispondenza del modello, ma questo produce ancora errori di tutti i tipi.

Dato che sono piuttosto nuovo nella lingua, potrei benissimo provare a fare qualcosa di impossibile qui, quindi per favore fatemi sapere se esiste una soluzione alternativa.


44
2018-02-01 16:09


origine


risposte:


Il sovraccarico è tipicamente il bugaboo dei linguaggi con inferenza di tipo (almeno quando, come F #, il sistema di tipi non è abbastanza potente da contenere classi di tipi). Ci sono un certo numero di scelte che hai in F #:

  • Usa il sovraccarico sui metodi (membri di un tipo), nel qual caso il sovraccarico funziona come in altri linguaggi .Net (puoi membri di overload ad-hoc, se le chiamate possono essere distinte dal numero / tipo di parametri)
  • Usa i vincoli "inline", "^" e dei membri statici per l'overloading ad hoc sulle funzioni (questo è ciò che la maggior parte dei vari operatori matematici devono lavorare su int / float / etc .; la sintassi qui è strana, questo è poco usato a parte la libreria F #)
  • Simula le classi di tipi passando un parametro extra del dizionario delle operazioni (questo è ciò che INumeric fa in una delle librerie F # PowerPack per generalizzare vari algoritmi matematici per tipi arbitrari definiti dall'utente)
  • Tornare alla digitazione dinamica (passare un parametro 'obj', eseguire un test di tipo dinamico, generare un'eccezione di runtime per un tipo errato)

Per il tuo esempio particolare, probabilmente userò solo l'overloading del metodo:

type MathOps =
    static member sqrt_int(x:int) = x |> float |> sqrt |> int
    static member sqrt_int(x:int64) = x |> float |> sqrt |> int64

let x = MathOps.sqrt_int 9
let y = MathOps.sqrt_int 100L

58
2018-02-01 18:50



Questo funziona:

type T = T with
    static member ($) (T, n:int  ) = int   (sqrt (float n))
    static member ($) (T, n:int64) = int64 (sqrt (float n))

let inline sqrt_int (x:'t) :'t = T $ x

Utilizza i vincoli statici e l'overloading, che effettua una ricerca in fase di compilazione sul tipo dell'argomento.

I vincoli statici vengono generati automaticamente in presenza di un operatore (operatore $ in questo caso) ma può sempre essere scritto a mano:

type T = T with
    static member Sqr (T, n:int  ) = int   (sqrt (float n))
    static member Sqr (T, n:int64) = int64 (sqrt (float n))

let inline sqrt_int (x:'N) :'N = ((^T or ^N) : (static member Sqr: ^T * ^N -> _) T, x)

Maggiori informazioni su questo Qui.


15
2017-09-28 20:40



Sì, questo può essere fatto. Dare un'occhiata a questo thread hubFS.

In questo caso, la soluzione sarebbe:

let inline retype (x:'a) : 'b = (# "" x : 'b #)
let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a

Avvertimento: nessuna verifica del tipo in fase di compilazione. Cioè sqrt_int "blabla" compila bene ma otterrai un FormatException in fase di runtime.


14
2018-02-01 16:42



Ecco un altro modo usando i controlli del tipo di runtime ...

let sqrt_int<'a> (x:'a) : 'a = // '
    match box x with
    | :? int as i -> downcast (i |> float |> sqrt |> int |> box)
    | :? int64 as i -> downcast (i |> float |> sqrt |> int64 |> box)
    | _ -> failwith "boo"

let a = sqrt_int 9
let b = sqrt_int 100L
let c = sqrt_int "foo" // boom

9
2018-02-01 21:21



Non togliere le risposte corrette già fornite, ma in effetti puoi utilizzare i vincoli di tipo nella corrispondenza dei modelli. La sintassi è:

| :? type ->

O se vuoi combinare controllo del tipo e casting:

| :? type as foo ->

2
2018-02-01 19:10