Live data from Hacker News

Cyclic dependencies are evil (2013)

fsharpforfunandprofit.com

51–60 of 125 posts

Re: Cyclic dependencies are evil (2013)

#51
post #18

Earlier quoted context omitted.

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. 1…

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.

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.

Re: Cyclic dependencies are evil (2013)

#52
post #42
post #16

Earlier quoted context omitted.

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?

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.

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!

Re: Cyclic dependencies are evil (2013)

#53
post #34

Earlier quoted context omitted.

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.

But how would you build the first GCC in that chain?

Using another C compiler, if none exists, create one in Assembly or other programming language in the target platform and go from there.

Re: Cyclic dependencies are evil (2013)

#54
post #7
post #3

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.…

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 . What you see as a cyclic dependency, I see as a data type that you haven't reified.

> and then a Bank which is a mapping of Account to Set

The problem is that this only lets you get customers from accounts, and not vice-versa.

And if you add a Map> 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.

Re: Cyclic dependencies are evil (2013)

#55
post #20
post #7

Earlier quoted context omitted.

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 . What you see as a cyclic dependency, I see as a data type that you haven't reified.

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.

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.

Re: Cyclic dependencies are evil (2013)

#56
post #3

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.…

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>
        }
        
        pub fn into_b(a: A) -> Option> { a.ref_to_b }
    }
    
    mod ModB {
        use crate::ModA::A;
    
        pub struct B {
            ref_to_a: Option>
        }
        
        pub fn into_a(b: B) -> Option> { b.ref_to_a }
    }
Am I missing something?

Re: Cyclic dependencies are evil (2013)

#57
post #54
post #7

Earlier quoted context omitted.

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 . What you see as a cyclic dependency, I see as a data type that you haven't reified.

> and then a Bank which is a mapping of Account to Set The problem is that this only lets you get customers from accounts, and not vice-versa. And if you add a Map > 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 b…

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.

Re: Cyclic dependencies are evil (2013)

#58
post #42

Earlier quoted context omitted.

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.

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!

If the code is implementing against interfaces, why would late binding mess up your IDE experience at all?

Re: Cyclic dependencies are evil (2013)

#59
post #3

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.…

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 > } pub fn into_b(a: A) -> Option > { a.r…

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 :)

Re: Cyclic dependencies are evil (2013)

#60
post #3

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.…

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.

Post reply on HN