Реализация конечного кодирования без тегов в F# с SRTP

Я хотел бы преобразовать мою версию F# OOP Tagless Final в типичный подход FP, и я думаю использовать статически разрешенные параметры типа классов классов из ОО.

Что я сделал

open System
open FSharpPlus

type UserName = string
type DataResult<'t> = DataResult of 't with
    static member Map ( x:DataResult<'t>  , f) =
        match x with 
        | DataResult t -> DataResult (f t)

создание SRTP мне нужно

type Cache = 
    static member inline getOfCache cacheImpl data =
        ( ^T : (member getFromCache : 't -> DataResult<'t> option) (cacheImpl, data))
    static member inline storeOfCache cacheImpl data =
        ( ^T : (member storeToCache : 't -> unit) (cacheImpl, data))

type DataSource() =
    static member inline getOfSource dataSourceImpl data =
        ( ^T : (member getFromSource : 't -> DataResult<'t>) (dataSourceImpl, data))
    static member inline storeOfSource dataSourceImpl data =
        ( ^T : (member storeToSource : 't -> unit) (dataSourceImpl, data))

и их конкретные реализации

type CacheNotInCache() = 
        member this.getFromCache _ = None
        member this.storeCache _ = () 

type CacheInCache() =
        member this.getFromCache user = monad { 
           return! DataResult user |> Some}
        member this.storeCache _ = () 

type  DataSourceNotInCache() = 
          member this.getFromSource user = monad { 
               return! DataResult user } 

type  DataSourceInCache()  =
          member this.getFromSource _  = 
              raise (NotImplementedException())        

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

let requestData (cacheImpl: ^Cache) (dataSourceImpl: ^DataSource) (userName:UserName) = monad {
    match Cache.getOfCache cacheImpl userName with
    | Some dataResult -> 
            return! map ((+) "cache: ") dataResult
    | None -> 
            return! map ((+) "source: ") (DataSource.getOfSource dataSourceImpl userName) }

и такой вид работает следующим образом

[<EntryPoint>]
let main argv =
    let cacheImpl1 = CacheInCache() 
    let dataSourceImpl1 = DataSourceInCache()
    let cacheImpl2 = CacheNotInCache() 
    let dataSourceImpl2 = DataSourceNotInCache()
    requestData cacheImpl1 dataSourceImpl1 "john" |> printfn "%A"
    //requestData (cacheImpl2 ) dataSourceImpl2 "john" |> printfn "%A"
    0 

Проблема в том, что я получаю предупреждение

конструкция приводит к тому, что код будет менее общим, чем указано в аннотациях типов

для обоих cacheImpl1 а также dataSourceImpl1 и поэтому я не могу использовать повторно requestData для другого случая. Есть ли способ обойти эту проблему?

1 ответ

Решение

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

let inline requestData (cacheImpl: ^Cache) (dataSourceImpl: ^DataSource) (userName:UserName) = monad {
    match Cache.getOfCache cacheImpl userName with
    | Some dataResult -> 
            return! map ((+) "cache: ") dataResult
    | None -> 
            return! map ((+) "source: ") (DataSource.getOfSource dataSourceImpl userName) }

В качестве примечания вы можете упростить функцию карты следующим образом:

type DataResult<'t> = DataResult of 't with
    static member Map (DataResult t, f) = DataResult (f t)

Я знаком с final tagless, но я не уверен, зачем вам использовать SRTP. Final tagless использует классы типов, и их можно эмулировать с помощью интерфейсов (см. Способ, которым scala эмулирует классы типов).

Подход похож (в основном тот же) на "объектную алгебру", который может быть реализован с использованием стандартных объектно-ориентированных конструкций.

Другие вопросы по тегам