1) Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity. Haskell is the same. Use restraint when bringing in new libraries and programming techniques. (as an aside, I have yet to encounter a single problem that I thought merited the use of arrows)
2) You also see this in other languages, though to a lesser degree. The same techniques we use with other languages work just as well in Haskell: As leadership, form a strong opinion on the best way to write control flow and ensure that everything uses a consistent style.
3) Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
4) In practice, this is almost never a big problem. Refactoring a pure function to an effectful function is easy and safe. In a production setting, almost all of your functions run in some monad already, so it's pretty rare to run into a situation where you suddenly need to percolate effects through many functions.
I've never been lucky enough to get a full-time Haskell job (nor did I try), so my experience is based on single-man development & prototyping using the existing stuff found on Hackage. I presume that it may be different for a huge organization that can afford to build the entire ecosystem from the ground up while enforcing design rules (and these rules must be created by an exceptionally brilliant architect!). But in other languages you don't have this situation.
> Many of us have also seen the C++ library or application that busts out the template metaprograms at every opportunity.
C++ and boost set standards a bit low, to be honest.
> Are your engineers running around implementing random research papers? This has nothing to do with Haskell.
Not research papers, just the usual business logic. The problem is that research papers set the code style. Also they are not "my engineers".
> In practice, this is almost never a big problem.
It typically is, because changing internal implementation is reflected in the interface, and for each invocation of the function change has to propagate through _the entire_ call stack to some place running in IO monad or whatever.
> almost all of your functions run in some monad already
You defy your own argument. If that's how you design application, what's the point of bothering with side effect isolation then?
For Example, Yesod gives you the Handler monad, which is based in IO, and really just exists to provide access to runtime/request information, so at the API handler level you can do anything you need to. But what's nice is that not everything has to live in IO, and so in places where it makes sense, such as a parser, we can say that parser doesn't need IO, because that wouldn't make sense.
And my point here is that just having the ability to separate between IO and non-IO is very useful; we also do not have to split every single effect down into it's own special, separate type; in many cases that would just be overkill.
I don't understand this.
If you have a monad like
do
...
_ <- doSomething arg1
let result :: Result = pureFun arg2 arg3
...
How is that not simpler and better than having that pureFun invoke a bunch of side effects and perhaps even mutate something that you have to be aware of? How does this take away the benefits of side effect isolation?Of course, I suppose there is always unsafePerformIO, at least for those late-night debug sessions.
For temporary debug output you should probably consider the Debug.Trace module before unsafePerformIO. It has a simpler interface and doesn't introduce the possibility of arbitrary I/O, just text output (and event logging if that's enabled in the RTS).
If you're able to run the code locally, there is a function called Debug.Trace.trace which writes messages to stdout without requiring IO.
If it's a production issue, all you need to do is figure out what is being passed to your function. It's a pure function, so you can always debug it locally once you've got that.
However, you can just log the output if you like without putting IO into the function. I see zero reasons why anybody would push such logging into production. It behaves exactly the same way on your own computer as it would in production - it's just a pure function.
However, disregarding the implementation details, here is the abstract problem: I have a program which is reading some complex data structure from IO, applying a complex, pure pipeline to it, and sending the reply back through IO.
Now, say the output is not correct in some way. Your only chance of debugging the issue is to somehow visualize some of the intermediate values, right?
This being a pure pipeline, you don't need to have logging in production. You could just log the input value, and when a bug occurs, run the same input value from the logs through a modified version of the original pipeline with some logging sprinkled throughout - you're right on this side.
Now, I expect that there are many situations where adding the logging in production is still preferable - maybe the pure pipeline is in development and some kind sof issues are frequently occurring, maybe the pipeline takes too long to re-run, so it's just easier to have the logs from the first run etc.
What I'd do is simply break down the larger pure function into smaller pure functions and sequencing them in the monad.
This way you would still retain purity with your functions but you could log every intermediate step by simply logging their output (and its corresponding input). Would probably make the code more maintainable as well.
From what I understand Haskell has great tooling for function profiling. Unfortunately, I got no experience with these yet so I can't say if they'd help you solve such problems.
Edit: Btw sounds like property based testing might work well as a solution to this kind of problem. It works really well with pure functions.
At my job, software is only have the engineering, we need to save half our brains for product/business issues.
I can't tell I had less problems understanding magic container IoC stuff than most Haskell concepts.
Not even that. Java's optionals are almost useless, needlessly verbose, you still don't have pattern matching, and there's also still null to contend with.
1) For a team of Haskell programmers reading Haskell code is just the norm. It's the same as for a team of Python programmers reading Python code. Also as a side-note: I never in my 15 years of professional life had a 4am call. Maybe I'm lucky, I don't know. In my experience automated tests, code reviews, sane deployment policies, etc... all minimise the chance of this ever happening. Haskell's powerful type system helps a lot here as it saves you from a whole array of problems upfront. I feel much more confident deploying Haskell code to production than code in any other language.
2) In commercial projects engineers tend to do things in sane and simple ways. This is not surprising really. Just cause you can does not mean you should. It's not Haskell specific but a general principle ie. KISS.
3) "Fancy research papers" at times can actually be really helpful. If you happen to have a problem that someone wrote a paper about that saves you time having to rediscover the solution. Having lots of research papers is imho a great strength of Haskell. That being said KISS from above applies here. If your problem is simple then do it simply.
4) Architecture in general is much less sensitive to change in Haskell than in other languages in my experience as the type checker guides you when you want to change your code. Refactoring in Haskell is pure joy. Re logging/configuration/etc... there are well-known patterns to deal with these as you would imagine, it's not like no one had to solve these problems in Haskell before. There are also tools (eg. Debug.Trace) in case you need some quick ad-hoc printf-style debugging. The same really as in any other language.
I don't want to write about all the pros of the language, others have done so already, just wanted to add my own professional experience to the mix.
If only weren't web only and run as a private project. An elm like language with llvm backend would be amazing
> If only weren't web only and run as a private project.
Yeah, the way the project is run is troubling.
If anything, it's the inverse: "isolation of side effects" means the architecture is more flexible and lego-like -- as opposed to a spaghetti mess.
And if you need to change interfaces, you are in for one of the smoothest rides, as the compiler will guide you every step of the way to implementing the change wherever it's needed.
Lets say you are making an RPG where you can equip items. You write a lot of pure functions and behavior for stat adjustments etc. Then the designer comes to you and say that items are no longer constants, every item needs a durability which goes down on use. In Java this would be a few line change, just add a new field in items and add a line to subtract it in relevant places. In Haskell you would need to bubble up the new item change to the global state and properly set it in each part of the code where you use items. If you aren't careful you can easily miss to properly bubble it up somewhere and the item doesn't get updated, or you might have the same item referenced in several places and now you'd have to refactor it to have a global item pool to since tracking down all places that should be updated is not feasible.
And then find out that since items used to be constants all instances of a given type of item are actually the same item object, so modifying one unexpectedly affects them all. Or worse, sometimes items are copied and sometimes they're shared by reference, so whether they're affected depends on each item's individual history.
One of the benefits of Haskell is that it forces you to think through these aspects of the API in advance. A mutable object with its own distinct identity and state is a very different concept from an immutable constant and the design should reflect this. Changing one into the other is not an action to be taken lightly.
But with assumption that code was working before and you had for example function: use :: Item -> Item, and you change duration in this function, what else do you need to change to "bubble" new state? I don't get this.
BTW What's the problem with global store and passing only IDs ? I feel like this is probably valid approach and anyway ECS are implemented in similar manner AFAIK - https://en.m.wikipedia.org/wiki/Entity_component_system
In practice this is not actually a problem. It just takes a little getting used to to get yourself out of the 'memory-address as identity' mindset that procedural languages have.
I don't think that's how you'd handle a state change in idiomatic Haskell. What you propose sounds very error-prone.
This is the same in any statically typed language. Actually the quality of Haskell/GHC's error messages are limited by constraint-based type inference and languages that have flow-based type inference do a better job IMO.
Surely not in any statically typed language. Languages like Java cannot encode the same useful information (or rather, you cannot force them to) as languages like Haskell. Specifically, you cannot make them enforce lack/presence of IO in their types. Most mainstream statically typed languages cannot do that, in fact.
Even then, in Haskell you can say "this doesn't do IO" which you cannot in most languages!
It's also just an example of the expressiveness of the type system, which "most statically typed" cannot enforce or sometimes even express.
I don't like the "simple language means understandable code" argument being thrown around like it was some obvious fact. I'm currently working on a project whose major component is a byzantine home-grown message passing framework written in pretty much Java-in-C++ (old school OO with liberal use of shared pointers. Not that I would call C++ in any form "simple", but I do think that C++ without template magic is simpler than Haskell with GHC magic, or, say, any Lisp with a lot of macros). Maybe it's a case of grass being greener on the other side, but I'd gladly take a monad transformer stack or a DSL written in Lisp macros over doing the same song and a dance over and over again just to implent a tiny new functionality, or trying to deduce the logic from someone else's sea of boilerplate.
I mean, taking this logic to extreme, one could say that code written in assembly is easy to understand: it's adding a value to EAX now, then it jumps over there...
This being said, Haskell does have something in it that encourages one to design a ballistic algebra that runs in the type system before even pointing a gun at one's foot.
Haskell is not even a total language, so type system doesn't magically divert gun from your feet. You can easily prove False in Haskell (there's a Prelude function for doing this!), enough said. And if you want types keep your feet safe, it's possible of course, at a _great_ cost, and not in Haskell.
Let's assume that your on call person is a developer. (If the person is not a developer, (s)he is not going to comprehend any language.)
1) If your application is written in Haskell, the on call developer would more than likely have been involved in the code base and reads Haskell. Your assertion about not comprehending it seems suspect.
2) You have a serious process issue if a developer is woken from a sound sleep to track down, correct, thoroughly test, and promote a fix for the bug in the middle of the night. The number of cases in which this happens should be close enough to zero to call it zero.
Your comment sounds like an attempt to cast Haskell as a purely academic language because it fails a (poorly) made up real world scenario. I'm NOT a Haskell-er so I don't feel a need to defend it. I do find the reasoning behind the criticism poorly thought out.
2) You’ve never worked in a company where a bug brought down prod and an on-call has to fix it?
2) Early on and even then rarely. After working in a hospital writing and supporting an electronic patient chart system, I found it hard pretending that anything short of life and death was serious enough to be woken over.
In my experience working in the largest Haskell team in the world, dealing with various applications and services in production: none of what you said is close to reality.
You've recounted your experience and detailed that you have seen issues with things in Hackage, and I won't claim your experience is invalid. I just won't let you claim your experience is that much more valid than someone else's without proof.
Especially point 4 sounds very painful. I frequently have to add logging or special case handling at different places in our code base.
It's all about how much do you isolate sideeffects from each other. If you go nuts with isolation, you loose flexibility. If you write all your code in IO monad (that means any code can have sideeffect), you are as in other languages where you can do sideeffect anywhere you want.
I'm still curious as to how to introduce variations though.
Frequently we find that we have to introduce "if (settings['special_x']) then DoSpecial else DoCommon" deep in the business logic to handle special cases that arise.
Assuming the existing function did not depend on the settings, how does one best introduce this dependency on the settings? In our code base we can cheat by effectively having the "settings" dictionary as a global variable, though we try to minimize this obviously. But in a pinch at 4am, that might be enough.
How about Haskell, is there a way to "inject" such settings without refactoring all the way to the top?
Source: 3 years of pure FP Scala in prod
Applications and building large systems are explicitly goals of Haskell. Haskell was never intended to be a research language.
>Haskell was never intended to be a research language.
Intended and actively tried not to be are two different things...
Rather than “avoid success” at all costs.
It’s a joke in the Haskell community that relates to the language’s early history and its ability to break things without much fuss. Things have settled down a lot more in the last few years, hence the shift in emphasis.
You can say the same thing about production Java code buried under layers of frameworks, factories, facades, proxies, aspects, annotations, etc. I don't see why readable, maintainable code can't be written as easily in Haskell as Java, C++, Python - you just have to make writing clean code a priority.
If you are stuck on a plane are you going to watch citizen kane or something from the last couple of years. What about an Akira Kurosawa movie? They top film critics' lists of the 'best' movies, but really they are some of the most influential movies of all time.
Lisp and Haskell aren't the 'best' languages to use the vast majority of the time, but they heavily influential and have contributed a lot to computer science.
Not a serious Haskeller at all, so not sure if this is a terrible practice, but for logging, `unsafePerformIO` could work in a pinch without changing types.
This is fundamentally why it's useless in practice. It requires you to anticipate/predict everything ahead of implementation.
It requires you to be perfect at Waterfall ( https://en.wikipedia.org/wiki/Waterfall_model ).
Real-world problems laugh at such idealism.
This is simply bs. Haskell is actually a language (that I know of) where, IMO, your initial choices matters least. And that's because it's gives you great refactoring experience.
Yeah right! I don't have to refactor extensible code.
In Ruby it's changing the attr_accessor line.
In Haskell objects are immutable, so you need lenses.
Don’t know what the state of Haskell refactoring is now but it looks like until very recently “great refactoring” was “change one thing and then manually fix compiler errors”.
Point me to something like that, for, lets say, Python. For example to error when I misuse return value of a function. Or when I decide that this string is no longer a string but a customer name. Or when my function will call IO operation, but it's not allowed to do so. Or when I add new field to a record and its not fully constructed somewhere.
- We are a statically typed language with significantly better support for refactoring than anything else out there
- But...
- Let's say Python
Let's say Java, why don't we? A language with significantly better support than "change one thing and manually fix compiler errors".
See IntelliJ's capabilities: https://www.jetbrains.com/help/idea/refactoring-source-code.... (there are 23 subsections)
And even Python's capabilities are significantly better than manually hunting through compiler errors (though obviously not as extensive): https://www.jetbrains.com/help/pycharm/refactoring-source-co...
On topic: Oh, these are great and I wish Haskell had more of that.
But these are "dumb" actions, really. How about:
* writing terrible code to get something working, then breaking it down and putting it back up as something production ready
* changing core data structure and updating all the code that uses it
* solving some problems in separate projects and combining them together
* copy-pasting code from another project and adjusting it to a current project
All of the above of course without running the program once in between, without changing the desired program's behavior etc.
And no, I don't think you can't do that in Java, I just think that Haskell is better at it.
* writing terrible code to get something working, then breaking it down and putting it back up as something production ready
Automated: Extract method, move code, pull members up/down, rename etc.
* changing core data structure and updating all the code that uses it
Depending on what exactly you're changing. From as simple as automated "update type signatures of all affected functions and methods" to automated "rename" to yes, hunting down compiler errors
* solving some problems in separate projects and combining them together and
* copy-pasting code from another project and adjusting it to a current project
Once again, depending on what and how you combine, using the things from above.
There are more powerful tools than "oh, let's run the compiler for each change and manually fix all the errors". The flow should be "let's run all the automated refactorings and then run the compiler to see if those missed something". And these automated refactorings are often reversible with a simple Cmd/Ctrl-Z.
Ah, yes. And many of these are available even for dynamically typed languages (but not as powerful and sometimes with caveats, for obvious reasons).
Java and Python are simply not languages that encourage (or even make possible) writing code that is easy under those changes. In Haskell you have immutability (if something is declared, that's its final value, so I can simply move it around), purity (code that is basically context independent), dumb data structures (no "enterprise boolean" with nontrivial internal state and ceremony to initialize), functional (no code attached to mutable data that may implicitly require data to be in special state), no nulls in flow control (that are invisible to a compiler, so yea good luck moving function around that expects no nulls), ergonomic creation of data/types (so you don't have strings/integers everywhere that are opaque to a compiler).
There is no contest, really. Maybe with some modern languages like Rust or TypeScript, but I haven't tried those.
However, most changes/refactorings I usually encounter are due to trivial problems that can be automated. And I'd love to have those tools for FP where a lot of changes are still just that: rename a function or a field in a data structure, extract a function, rename/move a module, update type signatures etc.
However, it looks like the stronger the static typing, the worse are the tools :) Not enough manpower to take care of them, perhaps?
As for Haskell, I think, it's a combination of manpower (so popularity, really) and the fact that the compiler wasn't developed with tooling in mind.
Thanks for conversation, stranger, and have a nice day!
This is interesting because another popular argument against Haskell is "Haskell's type checker can only check trivial properties that can be automated".
Either automating trivial things is valuable or it's not, and the anti-Haskell camp seems not to agree on which!
Being 'good at refactoring' is only a desirable feature if you are spending a lot of your time refactoring code.
Why are you spending so much of your time refactoring code? Because your language isn't extensible!
Not a fan of TDD, eh?
Not a fan of silver bullets.
But your comment is still odd. Refactoring seems to be a core tenet of modern programming (regardless of TDD). In TDD in particular it's a central part of how you work, and so your remark sounds odd unless you also claim you don't believe in TDD :)
>because we are talking about your comment on Haskell.
I am not in the habit of commenting about things in a vacuum. I am comparing Haskell to other tools in my toolbox.
I am sure that in the Haskell universe (isolated from the rest of reality) Haskell is the greatest thing ever.
It's solved in any language in which mutable data structures don't require a Rube Goldberg machine like lenses.
e.g languages which optimise for expressiveness over purism.
Languages which give me the expressive power to disregard the 'law' of non-contradiction if I happen to think it's a good idea to do so for whatever reason https://repl.it/repls/RoundRelevantExternalcommand
In any case, adding getters/setters doesn't imply extensibility and doesn't apply to FP languages (or rather: in FP languages you can add more functions). Getters/setters are often an anti-pattern anyway. Maybe you have a better example? In any case it's very easy to "extend" types in Haskell, it just looks different to what you'd do in Ruby.
As for expressive power, are you familiar with the "expression problem"? [0] There's almost always a trade-off your language is making.
I don't understand what you mean by law of non-contradiction in this context.
[0] the expression problem: https://en.m.wikipedia.org/wiki/Expression_problem
Of course I am making trade-offs, but so are you. It's just another word for making choices.
What I mean is literally being able to evaluate P ∧ ¬P as True, as an empirical test for a language's expressivity.
You can't do that in Haskell - it won't let you. The language is in control, not you.
In my problem-space not being in control is an anti-pattern.
Ruby is not more expressive than Haskell. In fact, I'm pretty sure it's less expressive, because it cannot say everything Haskell can, but Haskell can say everything Ruby can.
You say you understand everything is a trade-off but then you make claims about lack of expressivity ;) Do you know the expression problem?
Still don't understand your non-contradiction example.
>Haskell can say everything Ruby can
Go ahead and say P ∧ ¬P ⇔ True in Haskell.
Here is me saying it in Ruby: https://repl.it/repls/AccurateJauntyDimension
edit: ok, from what I've deciphered from your Ruby code, it's nothing special. You've declared a global boolean and every invocation of function "p" flips the value, so it's only a parlor trick and nothing to do with the expressivity of the language. Any language with side-effects and mutable globals will achieve this trick in the same manner. It's a trick because your first "p" and your second "p" aren't the same "p", so there's no real violation of the "law of non-contradiction".
What about the rest of my comment? The expression problem? That getters/setters are an anti-pattern in OOP languages (just ask Alan Kay...)?
> What about the rest of my comment? The expression problem?
Navigating around the expression problem is precisely what I am demonstrating. A language ought not [1] decide for me what I am allowed to express or not. I have a healthy disregard for linguistic purism [2].
Expressive power is the ability to say whatever I want to, whenever I want to. That is precisely what a para-consistent logic buys me - expressivity [3].
This is counter-intuitive to most people who have been taught to value consistency above all else (mathematicians, logicians). Non-contradiction is an axiom, not a law - it's a false authority. A man-made deity.
> it's nothing special
I didn't claim it's anything special - I merely claimed that I can say it. I did say it.
If "Haskell can say everything Ruby can" then go ahead and say it in Haskell. I am not saying it's impossible - but I a curious how you might navigate the Turing tarpit [4].
>It's a trick because your first "p" and your second "p" aren't the same "p"
First p and second p? You mean p@time(1) and p@time(2)?
Are 'you' not the same 'you' as the 'you' from 1 second ago, despite the ongoing changes in your body?
Ironically, that is the disconnect between pure functions and reality. Side-effects are the norm, not the exception.
>That getters/setters are an anti-pattern in OOP languages (just ask Alan Kay...)
That's an appeal to authority. Alan Kay doesn't get to make decisions for me. He gets to voice his opinion - I get to evaluate the pros and cons. The choice is always mine.
Like I said - In my problem-space lack of choice/control is an anti-pattern. Humans get the final say - not algorithms.
[1] https://en.wikipedia.org/wiki/Is%E2%80%93ought_problem
[2] https://en.wikipedia.org/wiki/Linguistic_prescription
[3] https://en.wikipedia.org/wiki/Paraconsistent_logic#Paraconsi...
This can be done trivially in Haskell making the global param explicit, and I'm sure with some trickery like the State monad you can even hide this. Maybe the syntax will be less neat, but this is a good thing: "hiding" things like side effects is bad.
The expression problem relates to your assertion about adding getters/setters, and judging by your reply it seems you didn't understand this...
So you're dismissing Alan Kay with no good reason. Furthermore, getters/setters have nothing to do with either extensibility or expressiveness. Why should I pay attention to your opinion?
data ParaBool = False | True
-- | Both - if needed for three valued logicThen implement your operators. Then everything is what it is defined to be. How can this be worse than sneakingly changing semantics of already defined things?
In a free world it should be legal to sell copper as gold, call the lies truth, and mix newborns in hospital - and people are just stupid if they want laws against these... ;)
In the free world, a pilot is free to recognize that the 'already-defined autopilot' is doing something stupid/dangerous (despite all the green lights) and is able to take control of the system at run-time - he doesn't have the luxury of fixing this bug at compile-time.
In a free world people are allowed to "sneakily change their minds" when they see an obvious denotational error.
Beware of bugs in the above code; I have only proved it correct, not tried it. --Donald Knuth
Type safety is not the same thing as system safety. The latter cannot be formalized - that is why humans are in charge. Not algorithms.
Making laws against airplanes crashing is not the same thing as stopping airplanes from crashing.
Here is a relevant extract from this paper[3]:
"All ambiguity is resolved by actions of practitioners at the sharp end of the system. After an accident, practitioner actions may be regarded as ‘errors’ or ‘violations’ but these evaluations are heavily biased by hindsight and ignore the other driving forces, especially production pressure. "
[1] https://en.m.wikipedia.org/wiki/Denotational_semantics
[2] https://en.wikipedia.org/wiki/Operational_semantics
[3] https://web.mit.edu/2.75/resources/random/How%20Complex%20Sy...
But your trick is neither. The law of non-contradiction refers to the same proposition P in both positions, simultaneously:
> "To express the fact that the law is tenseless and to avoid equivocation, sometimes the law is amended to say "contradictory propositions cannot both be true 'at the same time and in the same sense'" [0]
Yours isn't "at the same time" because each evaluation of P (which in your case is a programming function with side effects, neither a proposition nor a mathematical function) depends on the other evaluation to cause a side effect.
So you're cheating, you haven't expressed anything innovative or outside the realm of other languages (including Haskell), and you haven't broken the law of non-contradiction.
You are describing quantum superposition, but then you are straddling the classical and quantum paradigms when interpreting the LNC.
How many instructions/CPU cycles does it take to evaluate p ∧ ¬p? More than 1? Then what do you even mean by "simultaneously"?
>which in your case is a programming function with side effects, neither a proposition nor a mathematical function
You are making my argument for me. The LNC is an axiom of Mathematics/Logic. It's not a law as in a law of physics. And so if a physical system happens to violate it - it's hardly a big deal.
The real world actually contains things which are in two (or more) states simultaneously. We call them cubits and we use them to represent uncertainty.
What I have shown is a mutating getter. It's not supposed to be novel or original (I don't know why you are measuring me up to such ludicrous ideals). I am simply demonstrating to you a real-world scenario in which interacting with a system alters its state (deterministically or otherwise) which allows me to do the "impossible": evaluate p ∧ ¬p as True. It's just a race condition.
Side-effects are the norm, not the exception throughout the universe in general. Your classical computer would have no CPU registers, no cache, no memory, no persistence without side effects.
If the Mathematical religion frowns upon side-effects, why should I care about it when it clearly doesn't correspond to the universe I live in?
And if you are upset about my "trickery" (oh no! Physics is cheating!) here is an implementation that doesn't mutate global state.
A mutating getter doesn't violate the laws of non-contradiction, because a getter is not a proposition. In fact, if you use a getter at all, you're outside non-contradiction. The rest of your post is ridiculous, but feel free to keep equivocating this with quantum physics or cubits or whatever, instead of acknowledging that rather than breaking the law of non-contradiction, your real assertion is "my language can mutate global variables behind the scenes". Much less impressive, right?
Also, you keep avoiding to mention what your "problem space" is. I suspect this is because your "problem space" is rather pedestrian and doesn't require anything you claim it does.
Not upset, by the way. That's chicanery on your part. Usually done by people who know they are losing the argument ;)
Q.E.D you are admitting that non-contradiction it's an axiom of language and not an actual law. As in law of physics.
I don't expect you to answer my question re: the number of CPU cycles you consider as being a "simultaneous" evaluation of p ∧ ¬p least you had to admit the LNC is incoherent ;)
> That's chicanery on your part. Usually done by people who know they are losing the argument ;)
Isn't this what somebody who knows they are losing the argument would say ? ;) But if you are in it to win arguments - have a noddie badge. You win The Internet.
My problem-space is the CAP theorem. Global-scale distributed systems. Where most of the complexity is 'side effects'. But we call it network IO/communication/persistence/API calls etc.
Haskell would add zero value, because the bugs are rarely about type errors, and predominantly about Byzantine failures.
It's so pedestrian, it's probably your home page.
But it was you who introduced the law of non-contradiction in this conversation, then wrote a program that had nothing to do with it, and when called out you started with this nonsense about physics, cubits and what it means for "you" to be "you" and other unrelated topics. Either your program is about the LNC or it isn't, and if it isn't (as it turned out to be the case) why bring it up in the first place.
The CPU thing is nonsense and I don't see what I have to answer about it. In contrast, the Expression Problem is directly related to your initial assertion about extensibility, but instead you talked about something else. Getters/setters aren't about extensibility, by the way.
Finally, Haskell excels at IO, network, API calls, etc. People write distributed sysyems in Haskell -- why on earth would you think otherwise?
> It's so pedestrian, it's probably your home page.
I don't understand this. It reads like an attempt at trolling but I don't understand what it means. In which sense should I be offended? Google is my home page, does that help you somewhat?
I did do that. With the intent to determine whether the discussion is about denotational semantics (theory) or operational semantics (practice).
In theory there is no difference between theory and practice, but in practice there is...
>Either your program is about the LNC or it isn't
>The CPU thing is nonsense and I don't see what I have to answer about it
Until you decide on the 'CPU thing', I don't see how you can possibly decide whether ANY program is about the LNC or not.
Denotationally - it clearly is about the LNC as defined. It expresses precisely "P ∧ ¬P".
Operationally - it is about the LNC IF and only IF "P ∧ ¬P" can be evaluated simultaneously.
If your notion of "simultaneously" doesn't mean "evaluated in a single CPU cycle/instruction" then explain what you mean?
The leaky abstraction of CPU time is somewhat inconvenient to functional purists. As is the fact that strongly-typed Haskell reduces to untyped machine code.
Somewhere between typed and untyped Lambda calculus lies the choice of "right tool for the job".
>Getters/setters aren't about extensibility, by the way.
Would this be a good time to call out the fact that all you have been doing is Socratizing? Saying that X is not Y, says nothing about Y.
You asked me to give you an example, and trivially extending an object-model to support new fields in unstructured (read: untyped) data is not good enough for you.
If you think 'extensibility' has some universal/objective /theoretical meaning devoid of practical context, then go ahead and tell us what that meaning is.
> Finally, Haskell excels at....
How are you measuring that and against what objective standard for 'excellence' (or lack thereof)?
>People write distributed sysyems in Haskell.
People write (and have written) distributed systems in all sorts of languages.
In what measurable way is Haskell better?
>Google is my home page, does that help you somewhat?
Only in so far as Google doesn't use Haskell.
If Haskell offers value at that scale/complexity, then I imagine "building a better Google with Haskell" is a great idea for a startup?
I can. That's why I am entertaining him.
https://repl.it/repls/UnluckyBitesizedProcedures
(Trickery? No more than in your case)
Only if you choose to isolate all side effects from each other. Actually Haskell makes refactoring really easy, so you can adapt your implemention to changing requirements easily.
I wanted to make point that that #4 is not true.
Amortizing the up-front cost of design, into a continuous cost of refactoring every time the requirements change doesn’t make the original claim false.
You are still paying the price of rigidity.
(1) "Haskell is unreadable because the business logic gets buried under layers of technobabble." Not observed in practice, and not a property of the language anyway.
(2) List of many things that are supposedly about control flow. They aren't. Also implies that these things are incompatible with each other, which isn't true.
(3) Indeed, some developers try to reproduce research papers out of curiosity some of the time. That's a good thing.
(4) Mostly true, but true in any language, and not due to isolation of side effects.
These are textbook examples of straw manning: representing a caricature of the other side in order to easily argue against it. (But why? Who hurt the poor guy?)
That's a huge red flag.
As for the rest.. I don't think the language was the problem at that team.
Look, you've already fucked yourself by needing a 4am custom fix. That isn't a language problem.
It can be a language problem if the person who needs to do a 4am custom fix can't understand what the code is supposed to do.
Or to put it another way: I'd much rather be debugging some BASIC code at 4am than some Brainfuck code.
As I pointed out in my previous post, I do feel the language can be a non-trivial factor in allowing a dev like myself to be confident in developing a fix for my coworkers code at 4am.
However I don't know Haskell, so I have no idea how it fares in this regard.
Where I work now, on-call was long ago relegated to nothing more than a routing task. The on-call person takes the initial call, figures out who best can solve it, and contacts the person. The guy on my team who writes only in Javascript and Python would NEVER be in the position to make a critical fix overnight in a Java module. For those of us who work in Java, reading the language is trivial but understanding what the module is supposed to do from a high level is non-trivial. People who work in Haskell daily would be the same.
This is what I tried to call out. A Haskell developer called at 4am would have no more problem reading Haskell code than I would have reading Java code. While a Javascript developer should NOT try to issue a critical patch in a language (s)he doesn't work in, regardless if the language is Haskell, Python, etc. Any organization requiring you to do so is poorly managed.
Do you agree there's a spectrum here (with stuff like Brainfuck being on one end)? If so, I think it's a valid question to ask about a language.
Maybe Haskell is just as easy as Java, maybe it is slightly more difficult, maybe it is slightly easier.
For example, you can't really do embedded DSL's in Java, but you can in Haskell. If my coworker had used that and I had not yet had time to study it, could it be I would struggle a bit more with grokking his code? I dunno, at least to me it seems possible, but since I've not really used Haskell it might be entirely different.
I'm in complete agreement with you. My contention is that a person who works with a certain language, even Brainfuck, all day every day is going to be able to read it as easily as a person who works in a more "popular" language like Java: even if it's at 4am. Anyone who doesn't work in the language shouldn't be reading it with the goal of implementing a critical fix at 4am. That person should contact a team member who does work in the language.
I'm contending that the 4am scenario, as posed by the parent commenter, is a process issue not a language issue. If a manager requires that whoever answers the phone at 4am fixes the problem, even if the person has no knowledge of the language or requirements of the module, then his/her employees should be running to another job because that manager is bad for your health and livelihood.
For example, const helps me a lot when trying to understand C++ code. If I see a const reference, I know this bit of code I have in front of me can't modify whatever it is referencing. My language at work doesn't have const references, so I'm never quite sure if a call does modification as a side effect or not. This is the kind of stuff that I think can matter when trying to fix something at 4am.
Now, Haskell as I understand it is rather pure, so side effects like that are not a big issue. But on the other hand it allows for embedded DSL's from what I understand, which perhaps could be an issue if my coworker were to get creative. Though as I said I have no background to judge Haskell on this, I just thought it was a valid thing to be concerned about.
Give yourself time the next day to actually think about the problem with a clear head. At 4am trying to debug code, you're far more likely to cause more problems than fix things.
To me, I read this question the same as "Haskell's not useful when you're stuck in a well with a bomb ticking down." The language isn't the problem. It's the situation that's the problem.
All commenters who said "It's your own fault for writing crappy code, duh!" perhaps never worked with anything sufficiently complex and expensive.
And after a few refactorings.
Most errors have nothing to do with “unreadable code”.
In reality, it would be a sysadmin confirming dependencies for this application to run (in the system, network, storage, ...) are functioning as required. If there turns out to be a problem with the application itself, there's no time for development at that point: you roll back. I don't see how the paging developers thing would be able to provide any stability. It's too late for that when you're in production.
I am also the guy who puts metrics in place to monitor the health of the system and if any metrics breach alarming thresholds the owner of the service (me!) is automatically paged.
How does a sysadmin know that something is broken anyway, and how does a sysadmin know who needs to get paged? If a sysadmin can make this decision - so can an algorithm.
Much of this is in Google's SRE handbook[1]. The entire notion behind the DevOps concept was to make sure that you don't segregate development and operations.
The people who write crappy code must be the people who wake up at 3am when the crappy code breaks.
>It's too late for that when you're in production.
Bugs will always slip through testing, and things will break even in production. Nobody is perfect - not even Google.
So what do you do when rollback doesn't work, you've tried everything in the playbook but the system/service is still down? Whose job is it to understand how to recover your service? Surely you don't expect the sysadmins to be doing that? They don't understand the system. How could they? They didn't build it.
The problem had to be fixed right then as further delays would cause heavy disruptions of the operation for that day, which would have been very bad for our customer.
While support can deal with most issues, it's not entirely uncommon that us devs have to step up and either help fix the issue by analyzing the relevant code, or actually push a fix in the middle of the night.
I would never bring it to the production though, reasons being:
1) Production code should be understandable by an on-call person at 4 am. If business logic is buried under layers of lenses, monad transformers and arrows, good luck troubleshooting it under stress. And real systems do break, no matter type safety.
2) It's a research language, and a big part of the research is about control flow. And therefore haskell has _way too many_ ways to combine things: monad transformers of different flavors, applicative functors, arrows, iteratees, you name it. And libraries you find on hackage choose _different_ ways to combine things. In the business code you probably want to combine multiple libraries, and you inevitably end up with unholy mess of all these approaches. Dealing with it takes more time than writing business logic.
3) Developers look at these fancy research papers and try to reproduce them. As a result, very basic things become extremely hard and brittle. I saw a real-live example when applying a transform to all fields of a record took a team 2 days of discussion in slack, because "writing this manually?! it won't scale to record with 100 fields".
4) Architecture is extremely sensitive to the initial choices due to isolation of side effects. Because if you suddenly need to do something as simple as logging or reading a config in a place where it wasn't originally anticipated, you're in for a bumpy ride.