[HN Gopher] Cyclic dependencies are evil (2013)
       ___________________________________________________________________
        
       Cyclic dependencies are evil (2013)
        
       Author : nojito
       Score  : 94 points
       Date   : 2021-01-30 15:48 UTC (7 hours ago)
        
 (HTM) web link (fsharpforfunandprofit.com)
 (TXT) w3m dump (fsharpforfunandprofit.com)
        
       | layer8 wrote:
       | The generic way to remove a cyclic dependency is by Dependency
       | Inversion (not to be confused with Dependency Injection), so that
       | a mutual dependency _A_ <-> _B_ is transformed into the
       | noncircular set of dependencies { _A_ - > _BIntf_ , _BImpl_ - >
       | _BIntf_ , _BImpl_ - > _A_ }. That is, at least one node in the
       | dependency cycle is split into interface and implementation,
       | breaking the cycle.
       | 
       | Compilers for languages like C# and Java can deal with
       | (seemingly) circular dependencies within a set of classes to
       | compile, because when _A_ uses _B_ they only have to consider the
       | interface (type signature) of _B_ , not its implementation.
       | (Cyclic dependencies _between_ modules are still a problem, in
       | particular for build systems.)
       | 
       | I'm not really familiar with F# (so someone please correct me if
       | necessary), but in languages that use type interference to
       | determine function signatures, one consequence is that the
       | interface (type signature) of the function then depends on its
       | implementation, so you can't as easily invert the dependency as
       | when you have explicitly declared type signatures.
        
       | squibbles wrote:
       | I still cringe when I think back to some C projects from long
       | ago, where the dependency chains of headers and libraries where
       | unfathomably byzantine.
       | 
       | On a related note, lately I have been looking into some
       | biochemistry topics and their relation to computer science. It
       | seems that cyclic dependencies are possibly a requirement for
       | life, which makes faithful simulations of biochemical processes
       | an interesting challenge.
        
         | jimmaswell wrote:
         | The C language and gcc are a cyclic dependency too.
        
           | ReactiveJelly wrote:
           | Only if you don't consider time, or optional dependencies.
           | GCC 10 _can_ be built with GCC 10, but it was probably built
           | with GCC 9 the first time around.
           | 
           | If you can bootstrap it, and you can, then it's not really a
           | hard, unbreakable cycle, right? It's just an option that's
           | quicker than starting from tcc or whatever every time.
        
             | nyberg wrote:
             | But how would you build the first GCC in that chain?
        
               | pjmlp wrote:
               | Using another C compiler, if none exists, create one in
               | Assembly or other programming language in the target
               | platform and go from there.
        
         | dilawar wrote:
         | That is an interesting observation. I wonder whether the
         | cycling in chemistry is more about information flow then like
         | (negative?) feedback, it helps in making a stable system.
        
         | mattgreenrocks wrote:
         | I'm a compiler nerd at the moment. I have an inherent trust of
         | things that bootstrap themselves, as if it means they are
         | conceptually more pure. I'm not sure whether that is 100% well
         | founded, but it sounds similar to some of these observations.
         | :)
        
           | ReactiveJelly wrote:
           | I think self-hosting is just a little over-rated for
           | languages. It's good that Rust and C++ are self-hosting,
           | you'd expect those to be languages you can write a compiler
           | in.
           | 
           | But Lua and JavaScript satisfy their own niche just fine
           | without having popular runtimes written in themselves.
        
             | mattgreenrocks wrote:
             | Most languages don't aspire to be so conceptually-complete
             | that they insist on being bootstrapped. Which is fine, I
             | make my living with them.
             | 
             | Those that do, however, are immensely beautiful and there
             | is more to be learned from them.
        
             | TheBrokenRail wrote:
             | There are a few JS interpreters written in JS, don't know
             | about Lua.
        
       | lazulicurio wrote:
       | In a related vein, is there any sort of theoretical transform
       | that can be used to convert cyclic dependencies into a tree?
       | Similar to how lambda lifting eliminates free variables. (Other
       | than the obvious one of treating the entire cycle as one node)
        
       | qudat wrote:
       | For my FE projects I employ a module based code structure. If you
       | import a module, you get the whole thing. This lends itself to
       | many positives but one negative is it makes it easy to run into
       | circular dependencies. I wrote an article discussing my
       | architecture and how I solve circular dependencies in js.
       | 
       | https://erock.io/scaling-js-codebase-multiple-platforms/
       | 
       | In the article I argue that circular dependencies are a good
       | thing because they uncover poor code organization.
        
       | Smaug123 wrote:
       | EDIT: In fact I appear to have paraphrased the next post in the
       | series, https://fsharpforfunandprofit.com/posts/removing-cyclic-
       | depe... . Probably go and read that instead.
       | 
       | One pattern this nudges you towards is having a Domain.fs at the
       | top of each project. Since everything in your project wants to
       | talk in terms of the data types you've modelled, it makes sense
       | to put them all in the same place at the top. Moreover, once
       | you've done that, you're guided towards having your data types
       | really being "dumb" - algebraic data types only - because to put
       | actual behaviour into them is harder when all you have is
       | definitions of data types (and no associated modules). You
       | certainly _can_ write OOsagne using the Domain.fs pattern, but it
       | 's much harder and the code really smells when you do (because
       | the domain file gets super long).
       | 
       | The upshot is that your domain model appears explicitly at the
       | start of the project, which is a big win for anyone who comes
       | into the project and needs to learn quickly what's going on. By
       | contrast, nearly all the C# I've ever come across has the domain
       | spread across many files and all mixed up with implementation
       | details.
       | 
       | This is certainly not the only way to write F# - I've written
       | projects which have the domain spread across multiple files - but
       | it's one nice way to handle a small-to-medium-sized project.
        
         | mattgreenrocks wrote:
         | This is hexagonal architecture. I've yet to see anything quite
         | as nice as it once I started using it.
         | 
         | The only downside to hexagonal architecture is that you start
         | to see how mediocre most architectures are, and how frameworks
         | often cap how good your architecture can be by virtue of their
         | structure. Frameworks tend to resist being put on boxes, and
         | hexagonal architecture tries to put all external dependencies
         | in boxes.
        
       | bob1029 wrote:
       | Language constraints aside, the real world is not something that
       | can be cleanly modeled without the notion of circular
       | dependencies between things. Not very many real, practical
       | activities can be truly isolated from other closely-related
       | activities and wrapped up in some leak-proof contract.
       | 
       | Consider briefly the domain model of a bank. Customers rely on
       | Accounts (I.e. have one or many). Accounts rely on Customers
       | (i.e. have one or many). This is a simple kind of test you can
       | apply to your language and architecture to see if you have what
       | it takes to attack the problem domain. Most approaches lauded on
       | HN would be a messy clusterfuck when attempting to deal with
       | this. Now, if I can simply call CustomerService from
       | AccountService and vice versa, there is no frustration anymore.
       | This is the power of reflection. It certainly has other caveats,
       | but there are advantages when it is used responsibly.
       | 
       | If you want to understand why functional-only business
       | applications are not taking the world by storm, this is the
       | reason. If it weren't for a few "messy" concepts like reflection,
       | we would never get anything done. Having 1 rigid graph of
       | functions and a global ordering of how we have to compile these
       | things... My co workers would laugh me off the conference call if
       | I proposed these constraints be imposed upon us today.
        
         | monocasa wrote:
         | You would forget everything about a customer if they closed
         | their last account?
        
           | Smaug123 wrote:
           | No, of course you would maintain a Set<Customer> as well
           | (possibly even a Map<Customer, Account Set> for efficiency).
        
             | ImprobableTruth wrote:
             | ... if you model it that way you're not using cyclic
             | dependencies at all and this is in fact exactly how you
             | would model it in a functional way.
        
             | monocasa wrote:
             | Their whole premise is that
             | 
             | > Customers rely on Accounts (I.e. have one or many).
             | 
             | I'm giving a domain relevant counter example to that
             | premise.
             | 
             | And interestingly enough, that leads you to a clean way of
             | modeling it without the cyclic dependency as you've just
             | done.
        
         | Someone wrote:
         | "Customers rely on Accounts (I.e. have one or many)"
         | 
         | Alternative model: the Customer relation couples a Person to an
         | Account.
         | 
         | I think that's a better model, as it allows Person P to be a
         | Customer at bank B but not at Bank C, Person Q to be a Customer
         | at both B and C, etc.
         | 
         | It also allows you to model Persons before you model Accounts,
         | breaking the circularity, allows Companies to become Customers,
         | etc.
        
         | Ma8ee wrote:
         | In a relational database you would have a CustomerAccount table
         | keeping track of which accounts that belongs to which
         | customers. Customer would not know about Account, and vice
         | versa.
         | 
         | And what has reflection to do with this?
        
           | gorky1 wrote:
           | Reflection allows you to put the interfaces to Account and
           | Customer into a shared module and the business logic
           | implementations into their own mutually independent modules,
           | thus removing the cyclic compile time dependency.
           | 
           | You "wire up" the implementations at runtime, using
           | reflection.
        
             | Ma8ee wrote:
             | I still don't get it. I put the interface into a shared
             | module and the implementations in independent modules
             | without reflection. Why exactly would we need reflection to
             | do that?
        
             | Smaug123 wrote:
             | If I may, that sounds horrifying. Please, for the sake of
             | my sanity and the IDE, always do things at compile time
             | rather than runtime if you can!
        
               | ldlework wrote:
               | If the code is implementing against interfaces, why would
               | late binding mess up your IDE experience at all?
        
               | gorky1 wrote:
               | I've seen people learning about dependency inversion from
               | a book called "Clean Architecture", then proceeding to
               | apply it to every bit of code they write, to make it
               | "clean". It makes code difficult to trace by reading
               | alone. Indirection may be cheap, but it adds up.
        
               | Smaug123 wrote:
               | My heart sinks whenever I need to spend ten minutes
               | trying to work out which of the three different
               | implementors is actually going to be called on a
               | particular code path. For about a week of December, my
               | work was solely spelunking to track down which
               | implementations of an interface in C# were dead code and
               | so on.
        
               | gorky1 wrote:
               | It's called the "dependency inversion principle". People
               | don't like to call it "reflection", and heap various
               | layers like XML or dependency injection annotions on it,
               | but technically, it comes down to reflection at runtime,
               | as far as I've seen.
               | 
               | It certainly has its cost in additional complexity
               | through indirection, but it's better than creating cyclic
               | dependencies or giant balls of mud.
               | 
               | https://en.m.wikipedia.org/wiki/Dependency_inversion_prin
               | cip...
        
               | Ma8ee wrote:
               | No, you don't need reflection or annotations to use
               | dependency inversion. Why do you think so?
        
               | gorky1 wrote:
               | The question was "what has reflection got to do with it".
               | I've used reflection for dependency inversion, so I think
               | that's what it's got to do with it.
               | 
               | If you can do dependency inversion without reflection,
               | more power to you :-) We can't do classpath scanning in
               | the project I'm working on because of the size of the
               | classpath, and compile time configuration using direct
               | imports would introduce cycles, so reflection it is for
               | us, in one form or another.
        
         | brundolf wrote:
         | I think the OP is overly dogmatic and I agree with you that
         | sometimes you just need circular dependencies
         | 
         | That said, I don't see what circular dependencies have to do
         | with reflection, much less a functional style? For a trivial
         | example, Rust lacks reflection but the following (functional-
         | ish) code works just fine:                   mod ModA {
         | use crate::ModB::B;                      pub struct A {
         | ref_to_b: Option<Box<B>>             }
         | pub fn into_b(a: A) -> Option<Box<B>> { a.ref_to_b }         }
         | mod ModB {             use crate::ModA::A;
         | pub struct B {                 ref_to_a: Option<Box<A>>
         | }                          pub fn into_a(b: B) ->
         | Option<Box<A>> { b.ref_to_a }         }
         | 
         | Am I missing something?
        
           | Smaug123 wrote:
           | Could you give an example of a domain that naturally requires
           | circular dependencies? I have been trying lately to practice
           | seeing outside the functional-programming-tinted lenses, but
           | I need a bit of help to do so :)
        
             | brundolf wrote:
             | I think the GP's example is pretty reasonable. And like I
             | said, I still don't really see what this has to do with FP.
             | Clearly F# has trouble with it, but so does C++
        
               | Smaug123 wrote:
               | I don't think the GP's example is reasonable, and nor
               | would an SQL-writer. Could you give something I will find
               | it harder to wriggle out of?
        
         | IggleSniggle wrote:
         | But functional-only business applications already did take the
         | world by storm! Relational DBs is the defacto way data is
         | organized, and a Junction table is the way you model the data
         | so that a functional query (generally in SQL) can be made
         | against it.
        
           | username90 wrote:
           | SQL is not functional, it mutates global state.
        
             | perl4ever wrote:
             | SELECT?
        
         | dataflow wrote:
         | I actually don't follow your example. In your example what
         | exactly would CustomerService be responsible for that prevents
         | it from functioning unless it has a reference to
         | AccountService? AccountService is pretty obvious (add/remove
         | money, link other accounts, close account, etc.) but I'm
         | struggling to see what CustomerService would do on a per-
         | customer basis that it would need a reference to AccountService
         | for. The only thing I can imagine is a get/add/remove accounts
         | operation, but (a) you need nothing but an account ID for that
         | (not the actual AccountService class), and (b) you can (and I
         | think perhaps should?) model this as a database table managed
         | by the Account class where you're associating customer IDs with
         | account IDs.
        
         | valand wrote:
         | In the functional paradigm, language constraints aside, to
         | model this "story" would need these:
         | 
         | - The definition of Account
         | 
         | - The definition of Customer
         | 
         | - A ledger consisting of: List of Account, List of Customer,
         | List of Account-Customer relations
         | 
         | A clerk works on the records on the ledger with one hand and
         | one pen, a metaphor for the service process working on the data
         | in the database.
         | 
         | An act, such as money transfer, would be described as a
         | function.
         | 
         | A customer creating a new account for himself is written as `fn
         | createAccount(Customer, NewAccountData)` because from the
         | perspective of the clerk/bank manager/service the customer,
         | newAccountData, and the existing data in the ledger as objects
         | which the clerk/bank manager/service must move around in a
         | precise way.
         | 
         | The module which has 'fn createAccount` depends on the types
         | `Account` and `Customer`.
         | 
         | In english it roughly sounds like,
         | 
         | "the success of writing the rule of creating an account for a
         | customer depends on knowing the definition of Account and
         | Customer."
         | 
         | The function is not written as
         | `customer.accountService.createAccount(newAccountData)`,
         | because the clerk doesn't schizophrenically pretend to be the
         | customer and create an account for himself. The clerk just
         | receive request from the actual customer, writes new account
         | entry and customer-account-relation entry into the ledger,
         | that's it.
         | 
         | There's simply no need to call CustomerService from
         | AccountService and vice versa. There's no need for reflections
         | because data types are all available at compile time.
        
           | piva00 wrote:
           | > `customer.accountService.createAccount(newAccountData)`
           | 
           | I believe that most OO implementations would read
           | accountService.createAccount(customer, newAccountData)
           | 
           | Care to elaborate if that was the main point of the clerk's
           | schizophrenia criticism? Or if I'm misinterpreting just call
           | me dumb, haha.
        
             | valand wrote:
             | Oh, I meant to emphasis on that "customerService depends on
             | accountService and vice versa" thingy. While in my writings
             | "accountService is a member of customer" is a form of
             | customer depending on accountService.
             | 
             | But my main point is that it should not be written that way
             | at all.
             | 
             | Semantically, if we must include the subject, it should be
             | 
             | `theService.createAccount(Customer, NewAccountData)`
             | 
             | Or replace theService with CustomerAccountService or
             | whatever.
             | 
             | Writing that way, with functions depending on types, avoid
             | getting tangled from the so-called account-customer
             | cyclical dependencies. Because account and customer don't
             | need to know each other until a function needs to know both
             | of them. There's no such thing as `customer.getAccounts()`
             | because in the end the query would roughly look like
             | `getAccountsWhereCustomerIs(CustomerId)`.
             | 
             | You're not dumb. It's just me mis-writing due to the fact
             | that I'm writing this at 4 in the morning lol. Or maybe I'm
             | misinterpreted parent comment and that confuses you. In
             | that case, I'm the dumb.
        
         | kohlerm wrote:
         | circular dependencies at runtime are not really a problem and
         | often necessary. circular dependencies at build time/between
         | modules are evil.
        
           | jonhohle wrote:
           | Your build time is someone else's runtime.
        
         | zaphar wrote:
         | Sometimes I read conversations about software engineering
         | concepts by career developers and I get this sense that there
         | is this entire hidden world of other engineers that I have
         | somehow managed never to encounter.
         | 
         | Every engineer I've ever met who has been doing it for more
         | than a couple years universally decries circular dependencies.
         | Every one of them has come to that opinion via hard won
         | experience dealing with real world problems they encountered.
         | And yet comments like yours reveal there are other engineers
         | working in places where apparently the opposite is true.
         | Whenever I see that I wonder how this sort of self sorting
         | manages to occur.
        
           | ironmagma wrote:
           | Some problem domains have unique characteristics. I think the
           | closer a problem resembles nature, the more it breaks our
           | structures we want to impose on it.
        
           | dexwiz wrote:
           | Manifolds? Locally we want everything to be a straight line,
           | but when you zoom out far enough you see it was a circle all
           | long.
        
           | kayodelycaon wrote:
           | Possible reason: selection bias. If everyone else is fine
           | with circular dependencies or at least sees them as a
           | necessary evil... are they going to extremely vocal about the
           | current status quo when there they have no need to defend it?
        
             | Aeolun wrote:
             | In my experience, it's generally less experienced engineers
             | that will happily make a clusterfuck of circular
             | dependendies.
             | 
             | If a more experienced engineer does, it's generally
             | considered a necessary evil, but not a choice taken
             | lightly.
        
           | username90 wrote:
           | I've worked with very high level engineers at Google that
           | thought cyclic dependencies are fine. Sometimes they are just
           | the simplest solution and trying to design abstractions to
           | avoid it just creates a huge mess.
           | 
           | If you've working with Java at Google (Guice) makes you learn
           | to hate hiding circular dependencies behind dependency
           | injection, since your binary gets injected by some huge tree
           | of thousands of dependencies all that can create errors or
           | interfere with each other. And without static checking trying
           | to reason and fix those issues becomes really hard.
           | 
           | I strongly prefer using the languages actual type system to
           | create circular dependencies that you can inspect using well
           | known tools over that any day, better have a problem you can
           | see than hide the same problem to satisfy some tools
           | requirement.
        
             | jonhohle wrote:
             | I think something has been lost along the way with
             | injection and IoC that makes using a fully functional
             | programming language for object wiring a compelling
             | alternative for most engineers.
        
           | hrktb wrote:
           | To me, the parent is describing a functional circular
           | dependency that we all have to live with, but when we'll
           | design the system it will be masqueraded in a many to many
           | association in a third object/table and we'll proudly say "we
           | have no circular dependencies".
           | 
           | To me both are technically true, reality is messy, and we
           | hide the mess under imaginary constructs.
        
           | TeMPOraL wrote:
           | > _Every engineer I 've ever met who has been doing it for
           | more than a couple years universally decries circular
           | dependencies. Every one of them has come to that opinion via
           | hard won experience dealing with real world problems they
           | encountered._
           | 
           | They may not realize they have them - at runtime, in the
           | object graph, possibly indirectly. There's little difference
           | in principle between cycles in "static" code vs. runtime
           | state, but we often can't express them in the former, because
           | our languages don't specify the concept of dependency at
           | enough granularity (and some rely on a linear compilation
           | pass).
        
         | [deleted]
        
         | titzer wrote:
         | I don't understand how reflection is involved here? Don't you
         | just have a data model (schema) that can be interacted with?
        
         | Smaug123 wrote:
         | In F#, you would naturally approach this by defining a Customer
         | independent of the Account (e.g. just containing a name and
         | address), and an Account independent of the Customer (e.g. just
         | containing an ID), and then a Bank which is a mapping of
         | Account to Set<Customer>. What you see as a cyclic dependency,
         | I see as a data type that you haven't reified.
        
           | piaste wrote:
           | > and then a Bank which is a mapping of Account to
           | Set<Customer>
           | 
           | The problem is that this only lets you get customers from
           | accounts, and not vice-versa.
           | 
           | And if you add a Map<Customer, Set<Account>> to the Bank, you
           | now have to ensure the mappings are synchronized at all
           | times. Which, to be fair, is very straightforward as long as
           | Bank is immutable, but still kind of annoying.
           | 
           | But considering how incredibly common many-to-many
           | relationships are in business logic and especially in SQL,
           | I'm constantly surprised that there isn't a de facto
           | standard, efficient data structure to represent them. That
           | speaks to how entrenched cyclic relationships are in software
           | engineering, I suppose.
        
             | Smaug123 wrote:
             | Sure, but you wrap that up if you want. A Bank can contain
             | both maps, if you want, and you hide the methods that would
             | update individual maps, only exposing your wrappers that
             | update everything together.
        
             | valand wrote:
             | In the in-storage form (database), the mappings is
             | synchronized at all times.
             | 
             | Many-to-many relationships in the purest form is described
             | as records of ItemAId/ItemBId pairs. `List<{ account:
             | Account, customer: Customer}>`. There isn't any cyclical
             | relationship in this form.
             | 
             | From `List<{ account: Account, customer: Customer}>` one
             | can derive `Map<Customer, Set<Account>>` and `Map<Account,
             | Set<Customer>>`. Bank can have copy of both mappings and
             | use them as double index, synchronizing both mappings as
             | operations goes by, while at the same synchronizing both
             | mappings to the in-storage form. Or not!
             | 
             | We're talking mostly about data-modelling in a programming
             | language, which applies to the in-memory form. `Customer {
             | accounts: Set<Account> }` and A `Account { customers:
             | Set<Customer> }` is its in-memory form, a layer of
             | indirection from its actual form, the in-storage `List<{
             | account: Account, customer: Customer}>`.
             | 
             | If informations of Accounts, Customers, and the many-to-
             | many relationship of both are laid flat in its purest,
             | source-of-truth form, the in-storage database entries, why
             | are people suggesting that there is/should be a cyclical
             | relationship in its representative form, the in-memory
             | objects?
        
           | jonhohle wrote:
           | Which makes it easy to answer the question "what customers
           | are on this account" and hard to answer the question "what
           | accounts does this customer have". As a customer, I usually
           | want the latter.
        
           | im3w1l wrote:
           | This resembles sql a lot. Btw I'm surprised that few
           | languages seem to have in their standard library a many-to-
           | many container which supports efficient lookups in either
           | direction.
        
             | resonantjacket5 wrote:
             | Actually yeah that is kinda interesting. The couple times
             | I've had to do it, always ended up hand-rolling a 'class'
             | with hashmaps in both directions. I wonder why few standard
             | libraries have a both way look up structure.
        
             | iso8859-1 wrote:
             | Which languages support this at all? Are you talking about
             | ORM?
        
               | im3w1l wrote:
               | I'm talking about something like this
               | std::relation<Foo, Bar> foobars;         ...
               | auto& bars = foobars.forward(foo);         ...
               | auto& foos = foobars.backward(bar);
               | 
               | Edit: Boost apparently has it, see e.g.
               | https://stackoverflow.com/questions/1128144/c-maps-for-
               | many-...
        
               | gpderetta wrote:
               | Boost.Bimap is a special case (and built on top) of
               | Boost.Multiindex that allows indexing a logical table
               | with an arbitrary subset of fields.
        
           | JackFr wrote:
           | This came up in an application I had modeling college
           | football. A Team is a member of a Conference and a Conference
           | is made up of Teams. But during realignment a few years ago
           | when teams were switching all over the place it became
           | apparent that Teams and Conferences were truly independent of
           | each other and that their association itself was a first
           | class object which also needed to capture the Season/Year.
           | 
           | Not really rocket science but some times your model is
           | telling you something if you're willing to listen.
        
             | kd5bjo wrote:
             | This is almost exactly the motivational example that Codd
             | used when originally describing relational algebra. He
             | described 5 different data organization schemes for a
             | single problem and designed relational algebra to work with
             | all of them. This ability for the same program logic to
             | work with many different data storage layouts should make
             | changes like you describe less painful to implement.
             | 
             | (see page 2)
             | 
             | E. F. Codd. 1970. A relational model of data for large
             | shared data banks. Commun. ACM 13, 6 (June 1970), 377-387.
             | DOI:https://doi.org/10.1145/362384.362685
             | 
             | PDF: https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf
        
               | gpderetta wrote:
               | My dream, when I'll have a few years of free time, is to
               | design a language were the relational table is the
               | primary data structure abstraction.
        
               | kd5bjo wrote:
               | I'm working on a Rust library for this as my MS project.
               | It calculates the queries inside the type system, so
               | there's minimal runtime cost.
        
               | swirepe wrote:
               | This is a cool idea. What would you need to be able to
               | try this in 2 weeks, instead of a few years? What's the
               | lean slice?
        
               | kd5bjo wrote:
               | Relational algebra isn't that hard to implement if you're
               | willing to sequential-scan all of the time. The massive
               | complexity comes in the query planner: The point of
               | adding an index is to change the space-performance
               | tradeoff of certain operations. The system needs to be
               | able to take advantage of these data layout changes, or
               | there's little benefit over storing everything in flat
               | arrays.
        
         | sbelskie wrote:
         | I'm having a hard time understanding why you think F# is unable
         | to handle the domain model presented.
        
           | mattgreenrocks wrote:
           | It reads as an extremist take on Chesterton's Fence: "the
           | fence exists, therefore it is probably the only thing that
           | could've worked."
           | 
           | There is nothing in this contrived example that F# or Haskell
           | (gasp, pure functional) wouldn't handle with ease. It just
           | would be modeled differently from traditional langs. I'd
           | argue the alternative modeling forced by FP langs would be
           | superior.
        
             | hutzlibu wrote:
             | " I'd argue the alternative modeling forced by FP langs
             | would be superior."
             | 
             | Then I'd like to see that. The other example of the
             | functional someone gave more above, was not convincing to
             | me. Much more complicated then the straight-forward simple,
             | but evil cycle approach.
        
         | danielovichdk wrote:
         | Customers and Accounts are not cyclic.
         | 
         | A customer has an account. The customer is the domain root.
         | Hence there would be no accounts if there were no customers.
         | 
         | A customer does need to to hve an account. An account must have
         | a customer.
         | 
         | It's not cyclic in any way
        
         | dgb23 wrote:
         | I don't quite understand what you're trying to convey with your
         | example in regards to FP.
         | 
         | First of all, both data and functions are first class concepts
         | in the functional paradigm and are not glued together as
         | classes. So you're not modeling your domain as ,,account
         | service" and ,,customer service", but rather have data
         | representation of these concepts and functions that
         | query/reduce etc on those. In your example that would be a
         | relational model, described as sets of tuples/maps, and
         | relational functions do derive new data. There is no need for
         | circular dependence, because your operations are generic over
         | relational data.
         | 
         | I agree with the notion that the purely functional approach
         | doesn't dominate, for good reason. But more and more languages
         | are incorporating FP concepts since about a decade or longer,
         | at least the basic building blocks like closures, immutability
         | and function composition have gained massive traction and
         | eliminate the need for many of the complex patterns in
         | traditional, class based OO.
        
       | sbelskie wrote:
       | I've played around with F# over the years, but have only recently
       | started getting into seriously. The top down nature of
       | dependencies (both within a given file and for the files within a
       | project) is a little odd at first, but once you get used to it
       | feels quite natural.
       | 
       | If nothing else, it encourages (somewhat) common ways of
       | structuring projects and avoids a lot of bike shedding about
       | separation of concerns and project structure I encounter in C#.
        
       | titzer wrote:
       | Nice article. The first thing that came to mind when seeing the
       | title was "but circular dependencies just mean that your layers
       | are wrong and those should all be in the same layer" and funnily
       | enough that's _exactly_ what 's explained in the article!
       | 
       | What would be interesting would be to organize modules into named
       | layers--instead of just explicit 1-by-1 module dependencies (i.e.
       | edges). I have not seen a language do that yet.
       | 
       | Another interesting thing that I haven't seen touched on in more
       | mainstream languages is the idea of bidirectional interfaces. The
       | somewhat DSL-like NesC language had this, primarily driven by the
       | need to write device drivers that serviced interrupts (and
       | events).
        
         | klyrs wrote:
         | > What would be interesting would be to organize modules into
         | named layers--instead of just explicit 1-by-1 module
         | dependencies (i.e. edges). I have not seen a language do that
         | yet.
         | 
         | Correct me if I'm wrong, but I think you're describing
         | namespaces here
        
       | KingOfCoders wrote:
       | This was one of the reasons I used lots of Interfaces back in the
       | day with Java.
        
         | username90 wrote:
         | That doesn't remove circular dependencies though, it just hides
         | them.
        
       | danielovichdk wrote:
       | Funny to see those layered architectures/designs.
       | 
       | Tightly coupled dependencies are bad. Cyclic or not.
       | 
       | And if you do mange to to a cyclic dependency you are not
       | thinking about your design property. Imo
        
       | kstenerud wrote:
       | This is one of the things that frustrates me about go. They
       | started with this very good advice and then took it one step
       | stupider: Disallowing circular imports.
       | 
       | Why is this bad? It makes it impossible in many situations to
       | locate your public API at the top level. Let's say you have a
       | Widget interface and APIs to do things with those Widgets.
       | Eventually your library gets complex enough that you want to
       | separate functionality into subdirs. Now you have a problem: You
       | can't access the definition for Widget from the subdir because
       | importing the top level would create a circular import!
       | 
       | The only way to get around this problem is to have no code at the
       | top level, and put your public API in a subdir. Just be sure to
       | do this when first starting your library or you'll have to break
       | things.
        
         | caylus wrote:
         | You can deal with that issue by defining things in a submodule,
         | then importing and re-exporting them in the top-level (e.g.
         | `type Foo = subdir.Foo`). That even lets you move things into
         | the submodule without breaking callers. Other submodules can
         | then import from the submodule rather than the top-level.
        
         | xiphias2 wrote:
         | This is how libraries are exported inside Google (use /public
         | subdir for the public interface), and I guess with Go this got
         | into the language. But I think Go made much worse things than
         | this :)
        
       | codazoda wrote:
       | I've never tried F# so I'm not sure I'm exactly "on base" here,
       | but I actually _think_ this way. It 's frustrating to me when
       | dependencies happen automatically or when they can be used before
       | they are declared.
       | 
       | It helps me understand what's included if I can see a nice list
       | of all the dependencies. It helps me narrow down where the
       | problem is if I only have to address dependencies I know have
       | already loaded.
       | 
       | I learned to program in the 80's however, and it probably is a
       | bit "old school".
        
       ___________________________________________________________________
       (page generated 2021-01-30 23:01 UTC)