
Fighting Complexity in Software Development - atsapura
https://github.com/atsapura/CardManagement/blob/master/article/Fighting%20complexity%20in%20software%20development.md
======
Chyzwar
In mature OOP you have ways to write nice models and have good validations.
[https://guides.rubyonrails.org/active_record_validations.htm...](https://guides.rubyonrails.org/active_record_validations.html)

I will argue that the complexity of software development is not because of OOP
vs Functional. Tooling, documentation, quality of libraries and people are
what matter most. Ruby was a massive success is largely attributed to above.
We are humans, we can understand and deal with a fixed amount of complexity if
I can offload some of it to a framework, library or tool I will have more time
to work on my problem.

Every time I try to play with anything Functional I got hit by a bus of
undocumented frameworks(Erlang), multiple standard libraries (ocaml),
competing half finished implementations (lisp), arcane tooling (scala), no
tooling (Haskell) and broken tooling (F# on Linux).

~~~
jes5199
but have you ever had to maintain a ruby project after the first year? The
cost just goes up and up, and I think it’s because the language is so hostile
to static analysis.

~~~
losteric
Yes. I've been working on an corporate internal RoR tool that launched in
2010. Various engineers over the years continued releasing new features and
updating language/framework development... when I earlier this year,
development/maintenance cost was no higher than any other software of that
nature and age.

Cost only goes up and up when engineers go overboard with "clever" Ruby
magic... which is human error, don't blame the tool.

~~~
vharuck
>Cost only goes up and up when engineers go overboard with "clever" Ruby
magic... which is human error, don't blame the tool.

I've only used a few languages in production for my non-developer job, but
I've noticed very different frequencies of "clever" code between them.

R: This is my primary language. I love it, but it almost encourages clever
coding. There are 10 ways to do any task without even touching third-party
packages.

Python: My secondary language at work. Clever coding is definitely possible in
Python, but the language's syntax makes it easy to spot. Detection isn't as
good as prevention but it's better than nothing.

(I also grudgingly use SAS, but won't waste my time looking for ways it
encourages clear coding.)

------
flukus
I'm sure this is partly because I don't read F#, but it looks like they've
moved all the complexity into meta-programming madness, this is just being way
to clever to play code golf at a high level, this is exactly the sort of
complexity we should be fighting against.

Even the initial c# version was over complicated. The complex fluent interface
with lambdas and callbacks could be done with a few if statements that would
be simpler, faster and require no knowledge of the FluentValidation library.
Unnecessary getters and setters to satisfy the encapsulation gods.

If you want to fight complexity got back to basics, you can have a static
method returning a validation result with code like this:

    
    
      if (!string.IsNullOrEmpty(card.CardNumber) && CardNumberRegex.IsMatch(card.CardNumber))
        validations.add("Oh my");
    

Converting if statements to more elaborate constructs is creating complexity
not fighting it.

~~~
UK-AL
The problem is when you want validation errors which contains the field name,
and a descriptive error message. Oh you want them localised as well?

You then want each field to be validated individually. So you get an error for
each field which is wrong.

So you have if statements for each field creating a localised validation error
object then placing in a list.

You have 8 fields coming in on your request. It's starting to look like a big
method now with 8 if statements creating these localised validation objects.

You also want to share your validation rules between different use cases.

FluentValidation makes that quite quick and terse to achieve compared to
simple if statements.

~~~
flukus
> The problem is when you want validation errors which contains the field
> name, and a descriptive error message. Oh you want them localised as well?

So the above example would become something like this:

    
    
      if (!string.IsNullOrEmpty(card.CardNumber) && CardNumberRegex.IsMatch(card.CardNumber))
        validationContext.add("CardNumber", Localizer.MessageFor("InvalidCCNumber"));
      if (x.ExpirationMonth < 1 || x.ExpirationMonth > 12)
        validationContext.add("ExpirationMonth", Localizer.MessageFor("InvalidCCExpiration"));
    

Throw in some lambda's for the property name and static strings for the
message names if you really need to be type safe. Also I'm not sure if
FluentValidator handles this, but you need somewhere for root level errors,
not all errors map neatly to a property.

> You have 8 fields coming in on your request. It's starting to look like a
> big method now with 8 if statements creating these localised validation
> objects.

There's no local state, the errors are stored in a glorified dictionary, it's
a simple imperative series of if statements that anyone who's gone beyond
hello world in any language can understand. Big methods are not bad just
because they're big (not that 8 if statements is big), they're bad when there
is a lot of mutable state that the programmer has to track in their head,
validation logic rarely has this problem. It would be fine if there were 1000
properties because the complexity is flat.

> You also want to share your validation rules between different use cases.

So you make a function. It doesn't look like FluentValidator offers any
improvement here, it seems like custom rules with this library basically just
wrap a function call: [https://fluentvalidation.net/start#including-
rules](https://fluentvalidation.net/start#including-rules) or you create a
"function" at runtime with rulesets:
[https://fluentvalidation.net/start#including-
rules](https://fluentvalidation.net/start#including-rules)

> FluentValidation makes that quite quick and terse to achieve compared to
> simple if statements.

From the examples I'm not sure it's any quicker or more terse after you
include the extra boilerplate setup. All it seems to do is turn if statements
into where/must calls, for loops into RuleForEach calls and functions into
custom RuleSets. It also adds the complexity of using a library.

~~~
UK-AL
Your starting to build your own validation library inside that validation
context. Inside I presume it must be creating some kind of error object to a
list.

Next problem, rename a field on the object using a refactoring tool. You now
have to change validation code to change the field name. You may forget about
the validation code if your not looking at it. You have good tests though so
you would probably would catch it. But you want it to be automatic. Maybe
nameof?

The class is getting a lot responsibility, and you want to seperate validation
out into its own class responsible for that. Maybe extract to a validation
object which operates on a request/command class?

Might point is you eventually you end up building something like fluent
validation. With own set of default rules, validation classes etc. Maybe
fluent validation is overly complicated but I'd rather get the speed boost of
using a well tested library that I already know instead of gradually
refactoring into something custom.

~~~
flukus
> Your starting to build your own validation library inside that validation
> context.

I'm building a simple composite data structure, outside of NPM this is not
remotely a library. With a bit of luck not even that, I'm a bit rusty but I
think the MVC framework had one built in to handle this.

> Next problem

I already addressed it, a lambda function instead of a string that can extract
the property name, but your right that nameof might be a better option these
days.

> The class is getting a lot responsibility, and you want to seperate
> validation out into its own class responsible for that. Maybe extract to a
> validation object which operates on a request/command class?

I already have, the actual validation is in a static method somewhere, so it
has only one responsibility and the validation context is mostly a simple data
structure, that's not too much responsibility.

> Might point is you eventually you end up building something like fluent
> validation. With own set of default rules, validation classes etc. Maybe
> fluent validation is overly complicated but I'd rather get the speed boost
> of using a well tested library that I already know instead of gradually
> refactoring into something custom.

It's not an unbounded problem with lot's of gotchas down the line, it's a well
known and simple to solve problem that practically everyone has seen before.
What you're ignoring is the complexity of adding a dependency in general and
the complexity of this library in particular. Adding a dependency is not a
free lunch, it has a cost in time, mental overhead and maintenance. This
particular dependency increases the complexity of your code and delivers
practically nothing. As for the speed boost, assuming it's true does not mean
it reduces complexity, faster (initial) dev time very often comes at the cost
of creating more complexity.

------
kazinator
The main source of complexity is requirements. Gatekeeping against the influx
of requirements will keep complexity down.

Then there is unnecessary complexity from doing incomplete refactorings and
rewrites. If some code cannot handle the addition of a new requirement, it
should be replaced. Otherwise you add complexity that roughly takes the
logical (if not actual) form _if (these cases) { new code } else { old code
}_. And there is overlap! new code has taken over requirements for which old
code still exists, but because of some lingering requirements that _only_ the
old code handled, _all_ of it is still there (due to laziness, dependencies or
whatever). It's not obvious that some of that code is never used; someone
diving into it faces the complexity of figuring out what is the real payload
in production now and what is the historic decoy.

~~~
marcc
It's easy for us to blame "requirements" as the main source of complexity.
This isn't accurate. Software exists to serve the needs of the business.
Depending on the maturity and stage of the business or the software itself,
it's possible that there's a changing set of requirements. As developers, it's
out job to figure out how to deliver, not to say "no" to new enhancements and
requirements.

The main source of complexity is how we write software, not that the software
has requirements.

~~~
celticmusic
I think it's fair to say that changing requirements coupled with a limited
amount of time results in non-incidental complexity.

It's a systemic problem that results when the entire leadership stack isn't
aware of how good software is created. Because of the limited amount of time
and resources given, quite often it's a business/management problem.

And that's not to say that it isn't also a software dev problem. We've all
seen some horrific things. But I've also seen horrific things because there
was no one senior there because they wouldn't pay enough for it.

it's all intertwined, there's not a simple explanation. But changing
requirements is definitely a source of complexity.

------
Aeolun
I feel like I’ve responded this before, but I feel like people often attribute
their increased knowledge of how to develop systems without bugs to the new
fancy language they switched to.

Fact is they could build better software in the old language as well, assuming
they started from scratch.

~~~
DecoPerson
I strongly disagree. We use a strongly typed Lua-like language at my company
and it has everything you need to build decent applications, but we hit so
many bugs. It took me 12 hours over 5 days to make a simple modification to
the business logic (half of that was figuring out and fixing bugs). It took me
4 hours to write something far more complex in Rust with virtually zero bugs;
I attribute this almost entirely to sum types (Rust enums), a better type
system, an unforgiving compiler, async/await, a better module system, lifetime
checking, and an ecosystem of easy-to-grok libraries.

These things just make bugs disappear.

When it comes to IDE experience, the parts that I use often are mostly the
same between Rust and our language.

Edit: I'd say it's both, in a multiplicative manner. You need experience and a
good set of tools (the language itself being the most important tool) to write
good code fast.

~~~
gitgud
_I attribute this almost entirely to sum types (Rust enums), a better type
system, an unforgiving compiler, async /await, a better module system,
lifetime checking..._

This seems like an example of a language effectively abstracting common
complexities and pain points, which were probably discovered in earlier
languages...

~~~
agumonkey
I think it's fair to say that rust is the first mainstream languages bringing
these notions out of the woods. Who on earth knows about ML or cyclone ?
0.0001% of the programming crowd maybe.

World operates in spirals. We branch off trying things and then go back to old
forgotten ideas, etc etc

~~~
joe_fishfish
Kotlin has sealed classes which are basically the same as Rust's enums. It
also has async / await in the form of coroutines and a decent module system.
It also doesn't need manual memory management, which while unsuitable for many
applications does remove another potential class of bugs.

------
twodave
On the C# API I develop for we overcome these issues in a few ways.

1\. Our way of implementing DDD helps us organize code into infrastructure and
domain. Domain objects typically aren’t allowed to have external dependencies.
Infrastructure code is primarily for data access and mapping. Our API code
(controllers and event handlers) ties the two together.

2\. Given the above we are able to write a) very clear and concise unit tests
around domain objects and API endpoints and b) integration tests that don’t
have to bother with anything but data and other external dependencies.

The result is that when we go to ask, “How does the system respond given X?”
we can either point to a test we have already written or else add a new test
or test case to cover that scenario.

We can even snapshot live data in some critical areas that we can then drive
through our high level domain processes (we process payroll so it’s a lot of
data to consider). If someone wants to know how a particular pay run would
play out, they can just craft the data snapshot and write some assertions
about the results.

We also use FluentValidation (on API objects only) and test those as well (but
only if the rules are non-trivial).

------
realshowbiz
I’m quite happy to be seeing conversations about the benefits of simplicity,
boring tech, etc lately.

It’s a breath if fresh air from the sadly too common (IMO) flavor of the month
new tech promotion.

~~~
tw1010
Sometimes it's hard to disentangle if a conversation is having an upward
trending trajectory, or if you just happen to pay attention more to the links
that mention some subject you happened to have caught an interest in.

~~~
jes5199
Yeah, I’ve been hearing a lot about the Baader-Meinhof effect recently

~~~
charlieflowers
Was this intentionally a subtle self-referential joke? Or was that an
accident?

------
UK-AL
Domain modelling made functional is fantastic book(What this article is
inspired by). Taught me a lot about encoding business logic using functional
programming techniques.

Functional programming is basically my goto when there is complicated business
logic involved now.

------
redact207
I really love the concepts provided by Domain Driven Design (DDD), regardless
if you choose OOP or FP to implement it with.

It's fine if you want to choose C#, and there're better ways of addressing the
approaches to validation in OOP than were provided in the examples. Value
objects are a nice way to ensure strong immutable types like credit cards can
be created and passed around without requiring separate validation classes or
wild abstract base classes.

I like exceptions in C# - when I used to code that I'd make a lot of
domain/business exceptions that the code would throw anytime there was a
violation. Here I think Java is a lot stronger in that you are forced to
declare what types of errors can be thrown from a function so you have a
chance of handling them. In C#, Typescript, I'm finding myself having to lean
on codedoc "@throws" to do the same thing (though not as reliably).

That said, I generally am fine for most exceptions to not be handled and
instead bubble up "globally". If it happened because of an API request? Let
middleware map it back to a 400 Bad Request with the error body. If it
happened because of a message handled? Log it, retry the message until it gets
dumped to the DLQ. If it's not a violation, then it may not be an exception in
the first place, in which case it can be returned with a compensating action
performed.

I really like F#, but I struggled to find the actual benefit of it in this
article from a DDD perspective.

~~~
UK-AL
I find doing things like having types that flow through different stages ->
UnvalidatedEmail, ValidatedEmail, VerifiedEmail is a lot better in f#.

In c# you need to create a lot of value object classes for that. Or have some
kind of property inside the value object which indicates current state of the
email.

Even then you won't be able to exhaustive pattern matching on it to guarantee
each situation is handled.

~~~
Flow
Maybe I'm missing something, but what stops you from having Unvalidated<T> etc
wrapper classes in C#?

------
tomxor
I think the problem with OOP is that it is merely a pattern and yet has been
integrated into many languages in a very prominent way. This can mislead
people into thinking it's something more fundamental and generally applicable
- but it's not, it's just another pattern, which for some things works great,
and for others is terrible. Just like when you read that aweful code that
someone wrote just after reading a book on pattern x and forced it upon a
project, the difference is OOP is forced on almost everything.

Once you realise this it's fine, you just don't use those features when it
doesn't make sense.

------
jshowa3
An excellent click bait article for functional programming. For example, the
brief talk of validators, why couldn't you just put the validator in the
credit card object? The credit card validator should only be used in the
credit card object so the discussion about not being able to see it can be
accessed by simply navigating to the classes that compose the credit card.
Putting it in the constructor or other credit card methods fixes the not being
forced aspect (when I look at the FP example, it looks like it's doing just
this). And you can use the validator anywhere in the CC object.

------
yogthos
In my experience, one of the best ways to fight complexity is by reducing the
amount of things you have to keep in your head. In practical terms this
translates into being able to do local reasoning about your code.

I find that imperative OO style naturally leads to complexity. Passing
references to mutable data all over the place creates tight coupling across
your entire application. This makes it impossible to guarantee that any change
you make is local without considering every other place that references the
data. Meanwhile, objects are opaque state machines and programs are structured
by creating many interdependent objects.

These aspects make it pretty much impossible to tell what any particular piece
of code is doing just by reading it in large applications. The only option is
to fire up the debugger, get the app in a particular state and look at the
data. However, there are typically many ways to get into any particular state,
and it's really hard to know that you've accounted for them all. So, a
debugger is a heuristic at best.

FP and immutability tackle both of these problems head on. Immutable data
directly leads to the ability to do local reasoning about your code, and
allows you to write pure functions that can be reasoned about independently.
Meanwhile, data is not being abstracted inside opaque state machines that
provide ad hoc DSLs as their API. Instead, it's explicitly passed through
function pipelines to transform it.

~~~
tabtab
Much of the time the "big picture" isn't local. That's just the nature of the
big picture by definition. I find it better to put "big picture" stuff in the
RDBMS, including UI issues (see "Table-Oriented" nearby), and keep only local
details in code.

For example, the menus and navigation can almost all be tracked and managed in
the RDBMS. It's easier to query and study the structure that way because I can
sort, search, group, and filter it by any way --I-- please for any given need;
I don't want to be stuck with YOUR single grouping; I want to be the Grouping
God when studying the app. File-centric code _can 't_ do that (at least not
without an IDE that reinvents a database). Therefore, don't do it. Use code
where code is best, and RDBMS where RDBMS is best.

Code sucks at the big-picture and FP won't change that.

~~~
yogthos
I honestly have no idea what you're talking about.

~~~
tabtab
I suppose it would take a fully coded example really explain it. I cannot
provide one at this time.

------
twhitmore
Is it just me? I feel this is confused and adding complexity in some areas,
not genuinely optimal for simplicity.

I tend to feel that unexpected, unrecoverable exceptions are best treated as
just that -- exceptions. Applying a C-style function return value check seems
backwards.

And the "Interpreter"?? Proper purpose of Interpreter or DSL is for dynamic
(configurable or user-input) code, not to implement basic sequential flow and
the 'if' statement which the underlying language already provides.

~~~
couchand
Seems like a pretty classic case of second system syndrome to me.

------
tabtab
I agree that OOP is limiting, but Functional is not the fix. Table-Oriented-
Programming is the future. Your code snippets for validation etc. could be
associated how your domain prefers, and you can query them to be together by
field or by any other grouping as needed. You just have to make sure you have
the proper meta-data in place, such as field name/ID, entity, screen, event
type, etc.

File-centric code forces a hierarchical big picture structure, but many
relationships are either not hierarchical, or need additional non-hierarchical
ways to view/group them. Relational is more powerful and more flexible than
file systems. (It has some rough areas, but they can be worked around or
fixed.)

Start backward next time and think how you would LIKE your code organized,
forgetting about frameworks you know. If you do this often enough, you'll
realize RDBMS-based code management is where we should be heading. About 90%
of validation and field management could also be attribute-driven: data-
dictionaries would do most of the grunt work.

With OOP and FP you are forced into choices such as "should this be its own
class, or an object instance, or a group of classes for composition?" etc.
etc. When tablizing your event & validation snippets, you are not forced to
choose. They are grouped "by" anything you want, and by multiple groupings at
the same time: multiverse. I agree that FP is probably more flexible than OOP,
but it's also less disciplined: large FP systems look like the spaghetti-
pointer databases that preceded RDBMS. Hierarchical, logical, and pointer-
based DB's thrived for a while, but relational won, for a reason.

~~~
yogthos
Spreadsheet-like dataflow programming actually works quite naturally in FP.
See re-frame and javelin as examples:

[https://github.com/Day8/re-frame](https://github.com/Day8/re-frame)

[https://github.com/hoplon/javelin](https://github.com/hoplon/javelin)

~~~
tabtab
I didn't see any actual spreadsheets at those links. But my point was that
paradigm _matters less_ if you use table-driven designs (TDD). With TDD you
are dealing less with the issues of attaching & managing behavior to/with
structures (which the article seemed to emphasize), focusing mostly on
specific business logic and exceptions to rules (oddities). The majority of
your app would work without writing a snippet of code (except maybe regular
expressions for validation & formatting.)

Your actual code would be "dumber" and event-specific such that paradigm
differences matter less. Complex associations are managed via the RDBMS so
that code rarely has to manage them.

I should make a distinction between framework coding and application coding. I
won't say which paradigm is "better" for the first; I'm mostly focusing on the
application-side coding here.

------
timClicks
One really excellent resource that I'm making my way through currently is "A
Philosophy of Software Design" by Ousterhout. It's very practical and provides
several strategies for reducing complexity and identifying practices that
contribute to it.

------
ChicagoDave
Reducing complexity starts with abstracting bound contexts, understanding
relationships between them, identifying ubiquitous languages, understanding
the autonomous bubble pattern, and then worrying about code.

I’d add DAL should be shelved for a repository pattern and business layer
shelved for root aggregates and value objects.

It’s all in Eric Evans’ Domain Driven Design book that still carries enormous
weight.

------
hardwaresofton
I really support this kind of writing, I don't think this kind of stuff is
written about enough, and it's exactly what everyone learns the hard way
working on software projects. Skill wise, knowing the kinds of
reasoning/techniques that articles like these discuss is the difference
between "junior" and "senior" developers (though I very much dislike those
terms).

One thing I want to point out -- if at all possible do _not_ use decimals for
money:

> We could use decimal (and we will, but no directly), but decimal is less
> descriptive. Besides, it can be used for representation of other things than
> money, and we don't want it to be mixed up. So we use custom type type
> [<Struct>] Money = Money of decimal .

The custom type is a great idea (try to write code in languages that make this
concept easy, I suggest Haskell & type/newtype). The problem here is that
decimal is the wrong type for storing money[0]. Your first IEEE754 floating
point bug teaches you this, but in general trying to write code around
manipulating decimals can get very messy really quickly when precision is
involved in any case. Another example is JSON, JSON numerics are actually
_all_ floats under the covers, so this means if you store more precision or a
bigger number than it can handle, things can get wacky if you're not careful
-- this is one of the places where being "stringly typed" (and defining your
own unpacking to go with your domain types) can be very helpful.

Libraries like dinero.js[1] exist because of how surprisingly hard this
problem is, kind of like how moment[2] exists due to how hard dealing with
time can be.

[0]: [https://stackoverflow.com/questions/3730019/why-not-use-
doub...](https://stackoverflow.com/questions/3730019/why-not-use-double-or-
float-to-represent-currency)

[1]:
[https://github.com/sarahdayan/dinero.js](https://github.com/sarahdayan/dinero.js)

[2]: [https://momentjs.com](https://momentjs.com)

~~~
jorams
> The problem here is that decimal is the wrong type for storing money

Note that the code here is .NET, where decimal is a type explicitly for use in
financial calculations[0]. It is still floating point, which means you still
need to really watch what you are doing, but it's very high precision.

In general though, if your application allows for it, you should store money
using integers representing cents (or the relevant smallest unit for the
currency).

[0]: [https://docs.microsoft.com/en-us/dotnet/csharp/language-
refe...](https://docs.microsoft.com/en-us/dotnet/csharp/language-
reference/keywords/decimal) (this is a page for C#, but it is more useful than
the general page for Decimal)

~~~
couchand
> _It is still floating point, which means you still need to really watch what
> you are doing, but it 's very high precision._

The key difference between a decimal type (in any language) (regardless
whether it's fixed-point or floating-point) is that it's not BINARY floating
point. Yes, you need to watch what you are doing (shouldn't you always?)...
but you can limit analysis to the appropriate precision, without worrying
about binary conversion artifacts like 0.3 isn't 0.3.

> _In general though, if your application allows for it, you should store
> money using integers representing cents_.

What you're describing is a poor man's fixed point. Much better to just use a
fixed point decimal type so you don't need to remember to apply the scale
factor everywhere.

In any case, you can't get around the need to determine a ceiling in your
necessary precision.

------
austincheney
Perhaps the best way to take this seriously is to ensure developers RTFC (Read
The Fucking Code). While that sounds like a given it’s taken for granted that
developers actually do that before forming all manners of biased or incomplete
assumptions.

~~~
eloff
That's necessary, but the aim of good software practice should be to make it
as easy as possible for the reader of the code to follow and understand it.
Including skipping over well named functions which should not nest unintuitive
behavior.

~~~
james_s_tayler
This. All day long.

On the current code base I work on I swear every single one of my pull
requests contains large amounts of variable and method renaming to make it
easier for the next person because if it takes me half a day to compute an
understanding of what "var result = apiTotal - total" is actually doing then
it isn't named nearly clearly enough.

------
lacampbell
I love F#, but using F# instead of (type|java)script would add a lot of
complexity to my 'full stack'. Let's see I replaced my node.js backend with an
F# one, the following issues would arise:

1\. I now have two different languages for client and server, and can't share
e.g. validation code.

2\. Dealing with 2nd class linux support (no REPL)

3\. No library that combines the maturity and simplicity of express.js. Yes I
am aware of ASP.NET Web API and no I do not think that compares.

So as much as I love pipes and partial application and concise syntax, F#
would create an explosion of complexity, not fight it.

EDIT: This also follows the common trend I see of starry eyed functional
programmers who - to put it bluntly - don't seem to know what they are
criticizing.

 _Of course some things from here we can do in C#. We can create CardNumber
class which will throw ValidationException in there too._

A Result datatype with all the bells and whistles is what, a few hundred lines
in C#? I agree that it sucks that there isn't one built in, but if you like
them and you're stuck in C#, code one up and forget about the 'issue' ever
again.

 _But that trick with CardAccountInfo can 't be done in C# in easy way_

That example looks trivially translatable to an abstract class with two
concrete sub-classes to me.

EDIT 2:

The F# docs are awful. Struggling to find the API for the Result module.
There's a guide on how to use it, but when you look at the core namespace it's
missing.

~~~
avinium
I really don't think it adds complexity. Sure, it's going to be _different_ ,
so there's a mental hurdle to clear in learning how a new stack works. That
doesn't mean it's going to be more _complex_.

> 1\. I now have two different languages for client and server, and can't
> share e.g. validation code.

You could use Fable to transpile F# to Javascript, keeping a single language

> 2\. Dealing with 2nd class linux support (no REPL)

FSI is available under Linux, but I admit it's got some hairs on it. (FWIW,
FSI on Windows needs a lot more TLC too).

> 3\. No library that combines the maturity and simplicity of express.js. Yes
> I am aware of ASP.NET Web API and no I do not think that compares.

This is purely a matter of taste. While express.js is very accessible, I don't
think it's any more so than Giraffe (a wrapper around ASP.NET Core).

I'm all-in on F# now - once you're over the (mild) initial learning curve, you
see huge dividends from the smaller codebase, static typing, fewer null-checks
and drastically less testing required.

~~~
lacampbell
Hi, thanks for the open minded response. It's an issue I enjoy discussing.

 _You could use Fable to transpile F# to Javascript, keeping a single
language_

That seems like another explosion of complexity. Source maps, mapping F# to a
JS constructs when debugging, niche tooling, wrapping other JS libraries in a
nicely typed F# package... IME it's best to avoid transpiling anything more
exotic than typescript.

 _FSI is available under Linux, but I admit it 's got some hairs on it. (FWIW,
FSI on Windows needs a lot more TLC too)._

I appear to be mistaken - I remember I was excited when the dotnet tool came
out, but then they took away the REPL. FSI is mono only, right?

 _This is purely a matter of taste. While express.js is very accessible, I don
't think it's any more so than Giraffe (a wrapper around ASP.NET Core)._

Yes it's very subjective, we can agree to disagree here.

 _I 'm all-in on F# now - once you're over the (mild) initial learning curve,
you see huge dividends from the smaller codebase, static typing, fewer null-
checks and drastically less testing required._

I love F#, I used it professionally and it was a great experience. And you are
right, the conciseness is hard to beat. But the static typing/fewer null
checks thing is easily solved in JS land with the typescript compiler.

~~~
avinium
I think these usually end up with two people agreeing to disagree :)

Yes, you do lose source maps by transpiling, but in my (albeit fairly limited
experience), the type checking means I have drastically less debugging to do
in the first place. You're right, though, the tooling leaves a lot to be
desired (and this is also on the assumption you have TS definitions for third
party libraries - I agree it becomes a lot more difficult if you don't).

FSI is available in both Mono and .NET Core 3 (which is still in preview).

~~~
sasmithjr
I'm able to use FSI with the core-sdk 2.2.300, but I believe FSI is still in
preview regardless of which SDK you're using.

------
mapcars
``` type CardNumber = private CardNumber of string with member this.Value =
match this with CardNumber s -> s static member create str = ```

Haha, no thank you

>OOP languages for modern applications gives you a lot of troubles, because
they were designed for a different purposes

For which purposes if not software development exactly?

~~~
atsapura
> For which purposes if not software development exactly?

Well, if you recognize only 1 category "software development", I can't help
you. C was designed for software development as well, but you wouldn't chose
it to do web development, I hope? It's a good tool when you need to develop
something small and resource efficient. OOP fits good when you don't have much
of concurrency, but you manage complex states in memory (which you do with
help of objects and inheritance). And functional programming fits well when
you need to manage data flow applications.

And truth is in a big complex system you need a decent support of both FP and
OOP. Point is that languages like C# decently support only OOP.

------
GorgeRonde
Fighting stupidity, laziness and a misplaced love for typography in software
developers

