Создание и использование универсальной функции в нестандартном интерфейсе F #

#interface #f# #sequences

#интерфейс #f# #последовательности

Вопрос:

На данный момент это выше моего понимания. Я пытаюсь создать интерфейс, который выглядит примерно так.

 type IFetchData = 
     abstract FetchData: string -> seq<'a>
  

Приведенное выше объявление допустимо (и компилируется), но когда я начинаю его использовать, я получаю ошибку времени компиляции. Ожидалось, что это выражение будет иметь тип ‘a, но здесь имеет тип «то, что я в данный момент пытаюсь вернуть», т. Е. seq .

Однако мой пример использования выглядит следующим образом:

 type SampleFetchData() =
    interface IFetchData with
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter // compile error here
                }
  

Я не уверен, что я делаю неправильно. Все, что я хотел бы сделать, это позволить разработчику интерфейса иметь возможность писать любую функцию, которая возвращает универсальную последовательность либо seq<string> , seq<int> , seq<record type here> , seq<union type here> , и т.д.

Может кто-нибудь сказать мне, чего мне здесь не хватает?

Спасибо.

Ответ №1:

Если вы загружаете реализацию интерфейса с использованием отражения, то работать с ней будет довольно сложно. Проблема в том, что вы получаете объект типа obj . Вы знаете, что она реализуется IFetchData<'T> для некоторых 'T функций, но статически вы не знаете, для каких 'T . Это проблема, потому что вы не можете привести объект к какому-либо более определенному типу — если бы вы попытались использовать IFetchData<obj> , это не сработало бы, потому что вы не можете привести, например, IFetchData<int> к этому типу.

Я бы рекомендовал использовать неродовой интерфейс, который является довольно распространенным .СЕТЕВОЙ шаблон:

 type IFetchDataUntyped = 
  abstract FetchData : string -> System.Collections.IEnumerable

type IFetchData<'T> =  
  inherit IFetchDataUntyped
  abstract FetchData : string -> seq<'T> 
  

Когда вы загружаете реализацию с использованием отражения, вы можете привести объект к IFetchDataUntyped и работать с ним довольно разумным способом (используя Seq.cast для преобразования последовательности в более конкретный тип, если вы знаете тип элемента).

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

 type IFetchData =  
  abstract FetchData<'T> : string -> seq<'T>  // Note: Generic parameter here!
  

Комментарии:

1. Спасибо, Томас! Мне придется попробовать. В конечном счете, чего я пытаюсь достичь, это загрузить сборку, создать экземпляр типа, который в конечном итоге будет obj, а затем привести к интерфейсу для вызова метода. Затем я хочу взять результат (некоторый seq<‘T>) и сохранить для последующего использования по потоку. В конечном итоге она будет передана другой функции для обработки… задача целевых функций — узнать тип seq. Еще раз спасибо!

Ответ №2:

Вам нужно сделать что-то вроде

 type IFetchData<'a> = 
     abstract FetchData: string -> seq<'a>

type SampleFetchData() =
    interface IFetchData<char> with
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter 
                }
  

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

РЕДАКТИРОВАТЬ: встроенная версия magic

 let inline Fetchdata string obj=
   (^a: (member FetchData: string -> seq<'b> )(obj, string))

type SampleFetchData() =
        member self.FetchData str =           
           seq {
                    for letter in str do
                        yield letter 
                }

Fetchdata "hello" (new SampleFetchData())
  

Комментарии:

1. Спасибо за ответ, Джон. Я этого боялся. Я заставил ее работать, используя ваш ответ выше, но в конечном итоге это можно использовать с помощью отражения. И вот тут-то и возникает следующая проблема. По какой-то причине у меня возникают проблемы с динамической загрузкой и вызовом указанного выше элемента из-за универсальности интерфейса. Я надеялся, что мне не придется делать интерфейс универсальным, но у меня все еще будет универсальная функция. 🙂

2. Я обновил встроенную версию, которая может быть лучше, в зависимости от того, как работает ваше отражение.

3. Вау! Очень круто! На данный момент я придерживаюсь очень старой школы. Я в основном сопоставляю интерфейсы данного типа, чтобы определить, реализует ли он интерфейс выше, и если это так, то я создаю его экземпляр и пытаюсь привести его к интерфейсу выше и, наконец, вызываю элемент выборки данных. В вашем примере выше мне не нужно было приводить, я мог бы просто вызвать встроенную функцию выше с отраженным типом и параметрами. Я думаю, это правильно? 🙂

4. @cameron — встроенную функцию можно вызвать для любого объекта, если тип известен во время компиляции