rdnetto
Haskell has most of the same problems and Stack handles them easily. Each source file in a project can use different language extensions, all transitive dependencies are compiled from source, and there's even CI to re run tests whenever a package is bumped.
<T> T match(Function<X, T> foo, BiFunction<Y, Z, T> bar);
* in the cases where I don't have control over the common base type, I've written a builder pattern that constructs a sequence of predicate-function pairs and applies them appropriately. This looks like so: new PatternMatchingHelper()
.append(Foo.class, foo -> doSomething(foo.x))
.append(Bar.class, bar -> doSomethingElse(bar.y, bar.z))
.otherwise(x -> fallbackValue())
.apply(objectOfUnknownType);
The main disadvantage here is that it doesn't work well with generics, because the class objects have raw types.You could probably extend the second approach to get something close to the arbitrary predicates that pattern matching would provide, but the syntax wouldn't be nearly a clean as having it in the language.
If you're going to use it, I highly recommend the D Programming Language (book). It's extremely well written, and remains once of my favourite technical books.
No pure FP language will allow you to do that without the function's type signature reflecting that - it would need to return its value in some kind of state/async monad.
Now, if you're talking about FP in a traditionally OOP language, then I agree with you, but that's an apples to oranges comparison.
> Inexperienced OOP is a lot easier to deal with, because the entity level API validation is all you care about; you don't care about how hideously the internals were implemented as long as the tests pass. There can be any number of bugs and spaghetti inside the object, but you don't care, because you don't see it (until it manifests as bugs; but then you just add more tests and make them go away until all the tests pass).
This approach doesn't work for things that are difficult to test. e.g. you can only ensure your types are thread safe by reasoning about their use of locks, volatile, etc., because any attempt to test this is dependent on the implementation details of your runtime (e.g. the JVM) and the amount of load its under. Best case you get a flaky test, worst case you miss a bug that blows up in production.
But this is a tangent, because TDD is not specific to OOP - it works just as well with FP.
> but the point here is that someone has to define the module level interface, and how it behaves as a state machine (regardless of if the state comes in 'pure' and is passed around or is encapsulated internally); and that's OOP, not an inheritance hierarchy.
Pure state is an oxymoron. The idea is not that you pass a mutable argument in, but that you don't have the ability to mutate data at all. At best, you can return some instructions on how to mutate (e.g. the state monad), but its the consumer of your code who decides what to do with that, which prevents you from hiding side-effects, and simplifies testing.
Defining a module level interface and behaviour is just API design, not OOP.
In contrast, most (typed) FP languages support typeclasses / traits, which makes a lot of code considerably simpler.
How would you distinguish between them? Strong/expressive type systems encode the modelling in a way which is mechanically verifiable. e.g. by not permitting negative or fractional numbers to be submitted to a function which indexes an array.
I'd argue that type safety is only one benefit of strong types - the others being better documentation[1] and a design which is clearer and easier to reason about.[2] You can get both in a dynamically typed language, but they're much less common since the compiler doesn't require them.
[1] I've lost count of how many times I've looked at some function in Python or Javascript and had no idea what kind of values I was supposed to provide.
[2] My go-to example of this is how in Persistent (a Haskell ORM), values which have been inserted into the database (entities) and which have not have different types, since the non-inserted values don't know what their primary key is yet. This makes it much easier to reason about where the values are coming from and what needs to be done with them.
> Stack works only because it is an isolated repository where the maintainers have to take a lot of attention to make sure that new code doesn't break other code.
In practice, the only breakage checked for is compilation errors. The onus is still on library authors to declare compatibility bounds in their cabal files, which is why [PVP](http://pvp.haskell.org/) is still a thing.
I'm also not sure how accurate it is to call it isolated - it's pretty much at the centre of the ecosystem at this point.