Skip to content

Instantly share code, notes, and snippets.

@kevroletin
Last active April 16, 2018 04:14

Revisions

  1. kevroletin revised this gist Apr 16, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -124,7 +124,7 @@ First of all, OOP can be implemented in Haskell and there are several approaches

    1. Monad Transformers

    We prefer implementing several small "languages" for solving different problems and then combining them together. The main abstraction here is MonadTransformers library. Having ReaderMonad and StateMonad is not sufficient level of concerns isolation. So if you use Reader or State monad then it is possible to have really fine-grained separation of concerns by using type constraints like this:
    We prefer implementing several small "languages" for solving different problems and then combining them together. The main abstraction here is MonadTransformers library. Having different transformers like ReaderMonad and StateMonad is not a sufficient level of concerns isolation. So if you use Reader or State monad then it is possible to have really fine-grained separation of concerns by using type constraints like this:

    class HasDbConfig where
    getDbConfig :: DbConfg
  2. kevroletin revised this gist Apr 16, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -67,7 +67,7 @@

    5. Separation of concerns

    We usually implement different subsystems or different scenarios as objects. The rule of thumb is "the object should do a single thing and do it well". There are OOP patterns which help to organize interaction between an object in such a way that the rule of thumb works. Then we combine solution by calling object methods.
    We usually implement different subsystems or different scenarios as objects. The rule of thumb is "the object should do a single thing and do it well". There are OOP patterns which help to organize interaction between objects in such a way that the rule of thumb works. Then we combine solution by calling object methods.

    # FP

  3. kevroletin revised this gist Apr 16, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -89,7 +89,7 @@ First of all, OOP can be implemented in Haskell and there are several approaches

    There we have two options:

    * emulate OOP
    * emulate OOP and explicitly pass distionaries
    * use Typeclasses

    AFAIU even internally, type constraints are very similar to dynamic interfaces in OOP languages and require a compiler to pass Vtable for a typeclass:
  4. kevroletin revised this gist Apr 16, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -161,6 +161,8 @@ First of all, OOP can be implemented in Haskell and there are several approaches
    => User -> m ()

    That way we can implement multiple mini-languages for different activities that our application performs and then use it in our main AppMonad.

    (1) - You can automate is by TH and MakeClassyLenses from Lens library.

    2. Extensible effects. Free and Freer monads

  5. kevroletin revised this gist Apr 16, 2018. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -118,6 +118,7 @@ First of all, OOP can be implemented in Haskell and there are several approaches
    4. Dependency injection

    TODO: study existing DI frameworks in Haskell.
    TODO: study mocking of a part in a Monad Transformer stack

    5. Separation of concerns

  6. kevroletin revised this gist Apr 16, 2018. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -143,8 +143,8 @@ First of all, OOP can be implemented in Haskell and there are several approaches
    type AppMonad = ReaderT AppConfig IO ()

    storeUserToDb :: ( ReaderMonad r m
    , HasDbConfig r
    , IOMonad m)
    , HasDbConfig r
    , IOMonad m)
    => User -> m ()
    ...

    @@ -155,8 +155,8 @@ First of all, OOP can be implemented in Haskell and there are several approaches
    executeSql :: Text -> IO ()

    storeUserToDb :: ( ReaderMonad r m
    , HasDbConfig r
    , DatabaseIOMonad m)
    , HasDbConfig r
    , DatabaseIOMonad m)
    => User -> m ()

    That way we can implement multiple mini-languages for different activities that our application performs and then use it in our main AppMonad.
  7. kevroletin revised this gist Apr 16, 2018. 1 changed file with 84 additions and 3 deletions.
    87 changes: 84 additions & 3 deletions haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -71,15 +71,96 @@

    # FP

    First of all, OOP can be implemented in Haskell and there are several approaches which implement the different amount of concepts from OOP. I didn't read all of them because I want to concentrate on alternatives to OOP.
    First of all, OOP can be implemented in Haskell and there are several approaches which implement a different amount of concepts from OOP. I didn't read all of them because I want to concentrate on alternatives to OOP.

    * http://www.well-typed.com/blog/2018/03/oop-in-haskell/
    * https://arxiv.org/pdf/cs/0509027.pdf
    * https://yi-editor.github.io/posts/2014-09-05-oop/
    * https://programming.tobiasdammers.nl/blog/2017-10-17-object-oriented-haskell/

    1. Runtime configuration

    I know about the Handle pattern. Essentially it is an emulation of dynamic dispatch where you construct Vtables by hands. There is an extension of the Handle pattern where you combine global config and dependency injection with a registry of Handles into a single monad transformer:

    * https://jaspervdj.be/posts/2018-03-08-handle-pattern.html
    * http://etorreborre.blogspot.ru/2018/03/haskell-modules-for-masses.html?m=1

    2. Write your own implementation of a concept from 3rd party library

    There we have two options:

    * emulate OOP
    * use Typeclasses

    AFAIU even internally, type constraints are very similar to dynamic interfaces in OOP languages and require a compiler to pass Vtable for a typeclass:

    class Widget where
    draw :: WidgetDrawingMonad ()

    ...
    class Layout where
    addWidget :: Widget w => w -> IO () -- This method will receive Vtable if no inlining happens

    ...
    instance Widget MyWidget where
    draw = myWidgetDraw

    3. Extend existing implementation
    4. Dependency injection.
    5. Separation of concerns

    * Pass callbacks.

    concreteImplementation = abstractImplementation (\x -> x + 1) (\y -> y * 5)

    * TODO: Can I somehow use parametric polymorphism here? I definitely can in c++, how I would do it in Haskell?

    4. Dependency injection

    TODO: study existing DI frameworks in Haskell.

    5. Separation of concerns

    1. Monad Transformers

    We prefer implementing several small "languages" for solving different problems and then combining them together. The main abstraction here is MonadTransformers library. Having ReaderMonad and StateMonad is not sufficient level of concerns isolation. So if you use Reader or State monad then it is possible to have really fine-grained separation of concerns by using type constraints like this:

    class HasDbConfig where
    getDbConfig :: DbConfg

    class HasNetworkConfig where
    getNetworkConfig :: NetworkConfig

    data AppConfig = AppConfig
    { dbConfig :: DbConfig
    , networkConfig :: NetworkConfig
    } deriving (...)

    instance HasDbConfig AppConfig where ... -- (1)

    instance HasNetworkConfig AppConfig where ... -- (1)

    type AppMonad = ReaderT AppConfig IO ()

    storeUserToDb :: ( ReaderMonad r m
    , HasDbConfig r
    , IOMonad m)
    => User -> m ()
    ...


    We can go even further and restrict the amount of effects available in storeUserToDb function like this:

    class DatabaseIOMonad where
    executeSql :: Text -> IO ()

    storeUserToDb :: ( ReaderMonad r m
    , HasDbConfig r
    , DatabaseIOMonad m)
    => User -> m ()

    That way we can implement multiple mini-languages for different activities that our application performs and then use it in our main AppMonad.

    2. Extensible effects. Free and Freer monads

    TODO
  8. kevroletin revised this gist Apr 16, 2018. 1 changed file with 83 additions and 1 deletion.
    84 changes: 83 additions & 1 deletion haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,85 @@
    # Problems

    1. Runtime configuration

    Example: web services which read database configuration from config.yaml.

    An application reads configurations and chooses between different subsystem implementations.

    2. Write your own implementation of a concept from 3rd party library

    Example: graphical toolkits.

    We want our users to be able to extend framework/library functionality without modifying framework/library code. For example, in graphical toolkits, it is possible (and even desirable) to implement your own widgets.

    3. Extend existing implementation

    Example: graphical toolkits.

    Extend existing implementation by configuring/overriding some aspects of its behavior.

    4. Dependency injection.

    Example: mocking for testing.

    5. Separation of concerns

    # OOP

    # FP
    1. Runtime configuration

    Here we just declare an interface and take advantage of dynamic methods dispatch:

    interface Database {
    public <User> fetchAllUsers();
    public void deleteUser(User);
    }
    class PostgresDatabase implements Database { ...
    class MysqlDatabase implements Database { ...
    ...
    if (config.database.url.contains("postgres")) {
    return new PostgresDatabase(config.database);
    }
    else if (config.database.url.contains("mysql")) {
    return new MysqlDatabase(config.database);
    } else {
    ...
    2. Write your own implementation of a concept from 3rd party library

    Aka "Implement this interface" frameworks.

    Basically the same idea as in the previous point. Framework declared common interface and users are able to extend this interface.

    3. Extend existing implementation

    Aka "Fill the gap by overriding this virtual method" approach.

    The cool thing here is that extension happens at compile time.

    4. Dependency injection

    The object receives all its dependencies in the constructor and works with them via interfaces. That way it is easy to mock dependencies for testing.

    5. Separation of concerns

    We usually implement different subsystems or different scenarios as objects. The rule of thumb is "the object should do a single thing and do it well". There are OOP patterns which help to organize interaction between an object in such a way that the rule of thumb works. Then we combine solution by calling object methods.

    # FP

    First of all, OOP can be implemented in Haskell and there are several approaches which implement the different amount of concepts from OOP. I didn't read all of them because I want to concentrate on alternatives to OOP.

    * http://www.well-typed.com/blog/2018/03/oop-in-haskell/
    * https://arxiv.org/pdf/cs/0509027.pdf
    * https://yi-editor.github.io/posts/2014-09-05-oop/
    * https://programming.tobiasdammers.nl/blog/2017-10-17-object-oriented-haskell/

    1. Runtime configuration
    2. Write your own implementation of a concept from 3rd party library
    3. Extend existing implementation
    4. Dependency injection.
    5. Separation of concerns
  9. kevroletin created this gist Apr 16, 2018.
    3 changes: 3 additions & 0 deletions haskell-abstractions.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    # OOP

    # FP