Почему автоматическое повышение скорости в f #

Я реализовал интерфейс С# в F #, который выглядел примерно так:

public interface IThings
{
    Stream ThingsInAStream()
}

Моя реализация выглядела примерно так:

type FSharpThings() = 
    interface IThings with
       member this.ThingsInAStream() = 
           let ms = new MemoryStream()
           // add things to stream
           ms

Теперь я получаю сообщение:

The expression was expected to have type 
  Stream
but here has type
  MemoryStream

Я не понимаю MemoryStream IS. Поток. Я знаю, что могу использовать его как поток, например:

ms :> Stream

То же самое относится к [|"string"|] и IEnumerable<string>, он реализует интерфейс, и я могу явно использовать его, но он не работает автоматически.

Почему это работает?

let things:(IEnumerable<'a> -> 'a) = (fun f -> f.First())

let thing= things([|"";""|])

Это также автоматическое повышение уровня!

Ответ 1

Я думаю, что ответ от Николаса в целом прав. Разрешить автоматическое кадрирование на всех языках языка вызовет проблемы для вывода типа.

В принципе, компилятор может попытаться найти общий базовый тип типов, возвращаемых в разных ветвях, но это не так просто, как кажется:

  • Во-первых, должен ли он возвращать наиболее специфический тип или какой-либо другой тип? (Компилятор может найти наиболее конкретный, но, возможно, вы действительно хотите вернуть что-то более общее, чем то, что может быть выведено из вашего кода, - поэтому указание, что явно полезно.)

  • Во-вторых, с интерфейсами все становится сложно. Представьте себе, что две ветки возвращают два разных класса и реализуют интерфейсы IA и IB. Как компилятор решает, должен ли тип возврата быть IA или IB, или, возможно, obj? (Это большая проблема, потому что это существенно влияет на то, как можно использовать код!) Подробнее см. этот фрагмент.

    /li >

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

// The example from the question
let first (items:seq<'a>) = items |> Seq.head
let thing = first [|"";""|]

// Even simpler example - passing string as object
let foo (a:obj) = a 
foo "123"

Здесь аргумент array<string>, и функция ожидает seq<string>. Компилятор знает, что такое upcast для вставки (потому что он знает тип цели), и поэтому он делает это.

Ответ 2

Это контрагент с мощным механизмом вывода типа: Имея все явное, компилятор легче рассуждать о том, что истинно или нет.

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

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

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

//The cast will be determined by the compiler, because of _
result :> _