2015-05-22 5 views
0

Я не понимаю, почему следующий код не компилируетсяFSharp: Определение типа с общим

module GenericsTest = 
    open System 

    type Dog = { 
     name:string 
    } 

    type Apple = { 
     size:int 
    } 

    let get<'a> (id:string) = 
     Activator.CreateInstance<'a>() 

    let creatorInferred getAsParam = 
     let apple = { 
      name = getAsParam "some-apple" 
     } 

     let dog = { 
      size = getAsParam "some-dog" 
     } 
     (apple, dog) 

    let creatorWithTypeAnnotation (getAsParam:string->'a) = 
     let apple = { 
      name = getAsParam "some-apple" 
     } 

     let dog = { 
      size = getAsParam "some-dog" 
     } 
     (apple, dog) 

Если вы посмотрите на «творца ...» 2 функции - оба они дают ошибку компиляции ..

это выражение, как ожидается, иметь тип Int ... но здесь есть строка типа

Я могу видеть, что F # является infering типа возвращаемого метод getAsParam должен быть int, потому что он является первым, с которым он сталкивается. Однако почему он не решает использовать общий тип возврата?

Как вы можете видеть, я попытался использовать подпись функции в методе creatorWithTypeAnnotation, но это не влияет.

Я в тупике! Как заставить это признать, что функция getAsParam должна возвращать общий?

+0

В основном, когда он видит первый («Яблоко»), тип-вывод специализируется на 'getAsParam' в' string -> Apple', поэтому, конечно, ваше второе его использование (для 'Dog') должно завершиться неудачей - вы даже можете увидеть это, если вы удалите часть с ошибкой - компилятор должен предупредить вас, что он ограничил '' a'' на 'Apple' - попробуйте подумать о том, как вы напишете функцию, возвращающую реальный * общий *' 'a 'без обмана (отражение, умолчания, ...) – Carsten

+0

, но .. как заставить getAsParam иметь общий тип возврата? Примечание. Я могу сделать это, если один из моих аргументов для getAsParams - это «... но я не хочу этого делать –

+0

вы можете * обмануть * свой путь в это, если вы используете интерфейс вместо функции для' getAsParam' – Carsten

ответ

2

здесь быстрый F # -interactive сессии на то, что я имел в виду читов, используя интерфейс (он просто должен быть какой-то member - так это может быть метод на классе тоже, конечно):

> type IParam = abstract getParam : string -> 'a;;        

type IParam = 
    interface 
    abstract member getParam : string -> 'a 
    end 

> let createWith (p : IParam) : int*bool = (p.getParam "a", p.getParam "b");;    

val createWith : p:IParam -> int * bool 

> let test = { new IParam with member __.getParam s = Unchecked.defaultof<_> };; 

val test : IParam 

> createWith test;;                
val it : int * bool = (0, false) 

вы могли бы найти это не совсем легко реализовать некоторые Санер экземпляры IParam хотя;)

+0

Спасибо ...Я понимаю решение. Но для меня не имеет смысла, почему он должен работать как член, а не как func объявить в корневом модуле .... я понимаю, что мой пример не сделал этого. Почему функция-член отличается? –

+1

@MikeS: речь идет о типе фактического _value_ у вас, а именно 'getParam' - он должен быть конкретным типом _single_ для каждого вызова данной функции' creator'. В вашем коде он должен быть _either_ 'string-> int' _or_' string-> string' во время выполнения, он не может изменять типы наполовину через функцию. С интерфейсом 'getParam' является _allways_ type' IParam', и его метод 'getParam' может быть вызван любым количеством разных типов в течение одного экземпляра' creator'. – ildjarn

1

Как следует Карстен, переменные типа все во время компиляции. Если вы определяете getAsParam как string -> 'a, это не останавливает решение 'a во время компиляции. Поскольку во время компиляции не удается разрешить два разных типа, компиляция не удалась.

Рассмотрим вашего примера:

let creatorWithTypeAnnotation (getAsParam:string->'a) = 
    let apple = { 
     name = getAsParam "some-apple" 
    } 

    let dog = { 
     size = getAsParam "some-dog" 
    } 

Это также может быть объявлено таким образом (я буду использовать 'A вместо 'a, так как намеченная конвенции является то, что переменными типа строчным являются для компилятора выводимых типов):

let creatorWithTypeAnnotation<'A> (getAsParam:string->'A) = 
    let apple = { 
     name = getAsParam "some-apple" 
    } 

    let dog = { 
     size = getAsParam "some-dog" 
    } 

Теперь должно быть понятно, почему код не компилируется.

+0

Итак, теперь я понимаю, почему он не компилируется. Но могу ли я не просто сделать что-то вроде getAsParam «some-apple» ... чтобы заставить метод быть общим? –

+0

@MikeS нет, потому что 'getAsParam' не является методом. Это аргумент - переменная, которая имеет тип функции. Сам аргумент не может иметь параметры типа; то есть он не может быть независимо общим. Скорее, параметры типа относятся к определяемой функции, а именно: creatorWithTypeAnnotation. Это скорее похоже на C#, где вы не можете передать общий делегат в качестве аргумента для метода. Во время компиляции вы должны указать аргумент типа. Этот тип может быть общим параметром вызываемого метода, который аналогичен требованию, что 'getAsParam' является общим. – phoog

Смежные вопросы