7

Я редко сталкиваюсь с этой борьбой в настоящее время с F #, но опять-таки тип наследования гораздо реже встречается с F #, поэтому, возможно, мне просто повезло. Или у меня нет очевидного. Обычно, когда компилятор жалуется на незнание определенного типа, я отменяю порядок труб или композиционных операндов, и я готов.Вывод типа с трубопроводом или композицией завершается с ошибкой, когда нормальный вызов функции преуспевает

В принципе, при вызове функции, который работает как g(f x), он также работает как x |> f |> g или (f >> g) x. Но сегодня это не ...

Вот неаккуратно понятие доказательства правильности того, что я имею в виду:

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 

Обычно, это работает (по крайней мере в моем опыте). Линии с «откатом» броска:

ошибка FS0001: несоответствие типа. Ожидание MyExc -> 'a, но с учетом exn -> Ex. Тип «MyExc» не соответствует типу «EXN»

Не большое дело, не трудно обойти, но я, случается, приходится писать много кода, где композиция является проще/очиститель подход и я не хочу писать кучу полезных функций, которые я должен использовать везде.

Я рассмотрел гибкие типы, так как я предполагаю, что это проблема контравариантности, но я не вижу, как я могу применить ее здесь. Любые идеи сохранить эту идиоматику?

Обратите внимание, что если я перестраиваю, т. Е. Как Ex <<createExc>> Fail или используя оператор обратной трубы, я получаю ту же ошибку в другой части.

ответ

7

В этом случае компилятор F # ведет себя немного нерегулярно. В вашем примере вы хотите передать значение типа MyExc конструктору, который ожидает exn. Обработка объекта в качестве значения его базового класса является допустимой коверцией, но компилятор F # вставляет такие объединения в очень ограниченных местах.

В частности, он вставляет коверсию при передаче аргументов функции, но не вставляет их (например) при создании списка или возвращении результатов от функции.

В вашем примере вам нужно преобразование при передаче значения произвольному конструктору объединения. Кажется, что это происходит только тогда, когда непосредственно создание дела профсоюза, но это не произойти при лечении случая накидного как функция:

// 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! 

Таким образом, ограниченное множество мест, где F # компилятор применяет coersions автоматически включает различные способы вызова функций, но только прямой способ создания дел DU.

Это немного смешное поведение - и я думаю, что было бы разумно рассматривать дела DU как обычные функции, включая автоматическую вставку ковер, когда вы используете |>, но я не уверен, есть ли какие-либо технические причины, сделайте это тяжело.

+0

Отлично и проницательно, как всегда, спасибо Томасу. Похоже, мы согласны с тем, что это, безусловно, странное поведение. Я всегда думал (и, как правило, это правильно), что конструкторы DU ведут себя как функции, и я все время цепляю/составляю их. Глядя на подпись членов DU, они «выглядят» как функции. – Abel

2

Вы можете сделать типы обобщенными с ограничением наследования.

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

Да, это работоспособное решение (при условии, что типы доступны). Я надеялся лучше понять, почему 'f x' преуспевает, но' x |> f' не делает, или как сделать их обоих успешными. Оператор '|>' встроен, так что наверняка эти вызовы должны быть эквивалентны? – Abel

3

Тип вывода не работает с подтипированием (из которых наследование является одним случаем).H & M алгоритм просто не имеет понятия подтипирования в нем, и различные попытки его адаптации со временем не дали хороших результатов. Компилятор F # делает все возможное, чтобы разместить подтипы там, где это возможно, в виде специальных патчей. Например, он будет считать функцию «совместимой», когда фактический аргумент является супертипом формального параметра. Но по какой-то причине этот «патч» не преобразуется при преобразовании конструкторов union в функции.

Например:

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` 

На последней строке объединение конструктор T используется в качестве свободной функции, поэтому она теряет подтипирования патч.

Как ни странно, это работает для обычных функций (то есть те, которые не начинаются как союза строителей):

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

И даже работает точка-бесплатно:

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

Эта деталь предлагает обходное решение для вас: вы можете просто дать другое имя конструктору Ex, и трубопровод будет работать:

type Ex = Ex of exn 
let makeEx = Ex 

    static member createRes2 t = t |> createExc |> makeEx |> Fail // Should work now 
Смежные вопросы