Arquitectura / composición de la aplicación en F #

He estado haciendo SOLID en C # a un nivel bastante extremo en los últimos tiempos y en algún momento me di cuenta de que esencialmente no estoy haciendo mucho más que componer funciones hoy en día. Y después de que recientemente comencé a mirar F # nuevamente, pensé que probablemente sería la opción de lenguaje mucho más apropiada para gran parte de lo que estoy haciendo ahora, así que me gustaría intentar portar un proyecto C # del mundo real a F # como prueba de concepto. Creo que podría lograr el código real (de una manera muy poco idiomática), pero no puedo imaginar cómo se vería una arquitectura que me permita trabajar de manera tan flexible como en C #.

Lo que quiero decir con esto es que tengo muchas clases pequeñas e interfaces que compongo usando un contenedor IoC, y también uso muchos patrones como Decorator y Composite. Esto da como resultado (en mi opinión) una arquitectura general muy flexible y evolutiva que me permite reemplazar o ampliar fácilmente la funcionalidad en cualquier punto de la aplicación. Dependiendo de cuán grande sea el cambio requerido, es posible que solo necesite escribir una nueva implementación de una interfaz, reemplazarla en el registro de IoC y listo. Incluso si el cambio es mayor, puedo reemplazar partes del gráfico de objetos mientras que el resto de la aplicación simplemente permanece como antes.

Ahora con F #, no tengo clases e interfaces (sé que puedo, pero creo que eso no viene al caso cuando quiero hacer una programación funcional real), no tengo inyección de constructor y no tengo IoC contenedores. Sé que puedo hacer algo como un patrón Decorator usando funciones de orden superior, pero eso no parece darme el mismo tipo de flexibilidad y facilidad de mantenimiento que las clases con inyección de constructor.

Considere estos tipos de C #:

public class Dings
{
    public string Lol { get; set; }

    public string Rofl { get; set; }
}

public interface IGetStuff
{
    IEnumerable<Dings> For(Guid id);
}

public class AsdFilteringGetStuff : IGetStuff
{
    private readonly IGetStuff _innerGetStuff;

    public AsdFilteringGetStuff(IGetStuff innerGetStuff)
    {
        this._innerGetStuff = innerGetStuff;
    }

    public IEnumerable<Dings> For(Guid id)
    {
        return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
    }
}

public class GeneratingGetStuff : IGetStuff
{
    public IEnumerable<Dings> For(Guid id)
    {
        IEnumerable<Dings> dingse;

        // somehow knows how to create correct dingse for the ID

        return dingse;
    }
}

Le diré a mi contenedor de IoC que resuelvaAsdFilteringGetStuff&nbsp;paraIGetStuff&nbsp;yGeneratingGetStuff&nbsp;por su propia dependencia con esa interfaz. Ahora, si necesito un filtro diferente o lo elimino por completo, es posible que necesite la implementación respectiva deIGetStuff&nbsp;y luego simplemente cambie el registro de IoC. Mientras la interfaz permanezca igual, no necesito tocar cosasdentro&nbsp;la aplicación. OCP y LSP, habilitado por DIP.

Ahora, ¿qué hago en F #?

type Dings (lol, rofl) =
    member x.Lol = lol
    member x.Rofl = rofl

let GenerateDingse id =
    // create list

let AsdFilteredDingse id =
    GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")

Me encanta cuánto menos código es, pero pierdo flexibilidad. Si puedo llamarAsdFilteredDingse&nbsp;oGenerateDingse&nbsp;en el mismo lugar, porque los tipos son los mismos, pero ¿cómo decido a quién llamar sin codificarlo en el sitio de la llamada? Además, si bien estas dos funciones son intercambiables, ahora no puedo reemplazar la función del generador dentroAsdFilteredDingse&nbsp;sin cambiar esta función también. Esto no es muy lindo.

Siguiente intento:

let GenerateDingse id =
    // create list

let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
    generator id |> List.filter (fun x -> x.Lol = "asd")

Ahora tengo componibilidad al hacer AsdFilteredDingse una función de orden superior, pero las dos funciones ya no son intercambiables. Pensándolo bien, probablemente no deberían serlo de todos modos.

¿Qué más puedo hacer? Podría imitar el concepto de "raíz de composición" de mi C # SOLID en el último archivo del proyecto F #. La mayoría de los archivos son solo colecciones de funciones, luego tengo algún tipo de "registro", que reemplaza el contenedor de IoC, y finalmente hay una función que llamo para ejecutar la aplicación y que utiliza funciones del "registro". En el "registro", sé que necesito una función de tipo (Guid -> Dings list), que llamaréGetDingseForId. Este es el que yo llamo, nunca las funciones individuales definidas anteriormente.

Para el decorador, la definición sería

let GetDingseForId id = AsdFilteredDingse GenerateDingse

Para eliminar el filtro, lo cambiaría a

let GetDingseForId id = GenerateDingse

La desventaja (?) De esto es que todas las funciones que usan otras funciones tendrían que ser funciones de orden superior, y mi "registro" tendría que mapeartodas&nbsp;funciones que utilizo, porque las funciones reales definidas anteriormente no pueden llamar a ninguna función definida más tarde, en particular a las del "registro". También podría encontrar problemas de dependencia circular con las asignaciones de "registro".

¿Algo de esto tiene sentido? ¿Cómo se construye realmente una aplicación F # para que se pueda mantener y evolucionar (sin mencionar que se puede probar)?