2016-10-25 10 views
7

Rzadko mam tę walkę w dzisiejszych czasach z F #, ale potem znowu typ dziedziczenia jest o wiele mniej powszechny w przypadku F #, więc może po prostu miałem szczęście. Albo brakuje mi tego, co oczywiste. Zwykle, gdy kompilator narzeka, że ​​nie zna określonego typu, odwracam kolejność potoków lub operandów kompozycji i gotowe.Wnioskowanie typu z orurowaniem lub kompozycją nie udaje się, gdy normalne wywołanie funkcji się powiedzie

Zasadniczo, biorąc pod uwagę wywołanie funkcji, które działa jako g(f x), działa również jako x |> f |> g lub (f >> g) x. Ale dziś nie robi ...

Oto koncepcja proof-of-brudny, co mam na myśli:

module Exc = 
    open System 

    type MyExc(t) = inherit Exception(t) 

    let createExc t = new MyExc(t) 
    type Ex = Ex of exn 
    type Res = Success of string | Fail of Ex with 
     static member createRes1 t = Ex(createExc(t)) |> Fail // compiled 
     static member createRes2 t = t |> createExc |> Ex |> Fail // FS0001 
     static member createRes3 = createExc >> Ex >> Fail // FS0001 

Normalnie, to działa (przynajmniej z mojego doświadczenia). Linie z rzutem "nieudanym":

błąd FS0001: Niezgodność typu. Oczekiwanie na MyExc -> 'a ale biorąc pod uwagę -> Ex. Typ „MyExc” nie pasuje do typu „exn”

Nic wielkiego, nie trudno się obejść, ale zdarza mi się napisać dużo kodu w których skład jest łatwiejsze/sprzątaczka podejście i ja nie chcę napisać paczki funkcji użytkowych, które muszę umieścić wszędzie.

Spojrzałem na elastyczne typy, jak sądzę, jest to problem kontrawariancji, ale nie widzę, jak mogę go zastosować tutaj. Jakieś pomysły na zachowanie tego idiomatycznego?

Uwaga, jeśli przestawię, tj. Jako Ex <<createExc>> Fail lub za pomocą operatora cofania, kończę z tym samym błędem na innej części.

Odpowiedz

7

Kompilator F # zachowuje się w tym przypadku nieco nieregularnie. W twoim przykładzie, chcesz przekazać wartość typu MyExc do konstruktora, który oczekuje exn. Traktowanie obiektu jako wartości jego klasy bazowej jest prawidłowym wymiarem, ale kompilator F # wstawia takie spacje tylko w bardzo ograniczonych miejscach.

W szczególności wstawia sparcie podczas przekazywania argumentów do funkcji, ale nie wstawia ich (na przykład) podczas tworzenia listy lub zwracania wyników z funkcji.

W twoim przykładzie potrzebujesz współczynnika podczas przekazywania wartości do konstruktora dyskryminowanej sumy. Wydaje się, że tak się dzieje tylko wtedy, gdy bezpośrednio tworząc sprawę Union, ale to nie nie zdarzyć podczas leczenia sprawę Unii jako funkcję:

// foo is a function that takes `obj` and Foo is a DU case that takes `obj` 
let foo (o:obj) = o 
type Foo = Foo of obj 

foo(System.Random()) // Coersion inserted automatically 
Foo(System.Random()) // Coersion inserted automatically 

System.Random() |> foo // Coersion inserted automatically 
System.Random() |> Foo // ..but not here! 

Tak, ograniczony zestaw miejscach, gdzie F # kompilator automatycznie stosuje coersions zawiera różne sposoby wywoływania funkcji, ale tylko bezpośredni sposób tworzenia przypadków DU.

To trochę zabawne zachowanie - i myślę, że byłoby sensownie traktować przypadki DU jako zwykłe funkcje, w tym automatyczne wstawianie sfor, gdy używasz |>, ale nie jestem pewien, czy są jakieś techniczne powody, zrobić tak ciężko.

+0

Doskonały i wnikliwy, jak zawsze, dzięki Tomas. Wygląda na to, że zgadzamy się, że to z pewnością dziwny rodzaj zachowania. Zawsze myślałem (i zwykle to jest poprawne), że konstruktory DU zachowują się jak funkcje i cały czas je łączę/komponuję. Patrząc na podpis członków DU, wyglądają jak "funkcje". – Abel

2

Można utworzyć typy ogólne z ograniczeniem dziedziczenia.

open System 

type MyExc (t) = inherit Exception (t) 

let createExc t = MyExc (t) 
type Ex<'t when 't :> exn> = Ex of 't 
type Res<'t when 't :> exn> = Success of string | Fail of 't Ex with 
    static member createRes1 t = Ex (createExc t) |> Fail 
    static member createRes2 t = t |> createExc |> Ex |> Fail 
    static member createRes3 = createExc >> Ex >> Fail 
+0

Tak, jest to praktyczne rozwiązanie (zakładając, że typy są dostępne). Miałem nadzieję, że lepiej zrozumiem, dlaczego 'fx' się powiedzie, ale' x |> f' nie ma, lub jak sprawić, by obaj się powiodło. Operator '|>' jest zainicjowany, więc z pewnością te połączenia powinny być równoważne? – Abel

3

Wnioskowanie o typ nie działa dobrze z podtypem (którego dziedziczenie ma jeden przypadek).H & Algorytm M nie ma po prostu pojęcia podporządkowania, a różne próby dostosowania go w czasie nie przyniosły dobrych rezultatów. Kompilator F # dokłada wszelkich starań, aby dopasować podtytuł, gdzie jest to możliwe, w postaci specjalnych poprawek. Na przykład uznałoby funkcję "kompatybilną", gdy faktyczny argument jest nadtypem formalnego parametru. Ale z jakiegoś powodu ta "łata" nie przekłada się na konwersję konstruktorów związków do funkcji.

Na przykład:

type U() = inherit exn() 
type T = T of exn 

let g f x = f x 

let e = U() 
let a = T e  // works 
let b = g T e  // compile error: `e` was expected to have type `exn`, but here has type `U` 

Na ostatniej linii, konstruktor unia T służy jako wolnej funkcji, więc traci poprawkę Subtyping.

Co ciekawe, to działa dla zwykłych funkcji (czyli tych, które nie zaczynają jako konstruktorów Union):

let makeT u = T u 
let a = makeT e  // works 
let b = g makeT e // also works! 

I punkt darmo to nawet działa:

let makeT = T 
let a = makeT e  // works 
let b = g makeT e // still works! 

Ten szczegół sugeruje obejście problemu: możesz po prostu podać inną nazwę konstruktorowi Ex, a orurowanie będzie działać:

type Ex = Ex of exn 
let makeEx = Ex 

    static member createRes2 t = t |> createExc |> makeEx |> Fail // Should work now 
Powiązane problemy