HN user

joel_ms

88 karma
Posts1
Comments67
View on HN

Crypto really is the grotesque despressive clown staring our desperate civilization in the face, while futily screaming about our all consuming absurdity.

Crypto works fine in a world that is not addicted to burning fossil fuels.

Unfortunatly, we live in a world that is not only desperately addicted to them, but literally subsidizes the consumption of fossil fuels.

I'm personally of the opinion that less is more.

I used to have this position as well, writing everything in either assembly or pure lambda calculus, to avoid all these pesky higher level language features.

So scala is neither taken over or destroyed and the author of this post stresses multiple times that he does not suggest that this approach by adopted by the rust community, even concluding with:

But Rust is not Haskell. The ergonomics of GATs, in my opinion, will never compete with higher kinded types on Haskell's home turf. And I'm not at all convinced that it should. Rust is a wonderful language as is. I'm happy to write Rust style in a Rust codebase, and save my Haskell coding for my Haskell codebases.

How is any of this fanatical?

In many cases there are actually several layers of popups obscuring the content, and some are delayed so they only pop up after you start reading the content.

I swear I've had ones that popup as I move the mouse to close the tab.

Thank you!

I was more wondering how time leaks and laziness relates to denotational vs operational semantics? I couldn't find anything about either in the links (they seem to be general descriptions of haskell and laziness..?).

To be honest, I find the idea of passing a parameter to Log() (whether it's the value or the function) that your don't actually want logged, so that you can shoehorn two statements into an expression, abhorrent.

You've basically reimplemented Haskell's Debug.Trace[0] :)

The difference is of course that Haskell doesn't really have statements in the same way that C# does, so in Haskell it's nescessary to turn the log/trace-function into an identity function after you've applied the message-string.

I agree that it is ugly, and in any serious project I would use a proper logging setup, but I almost feel like the ugliness is a feature not a bug. It's like an extra cost when doing debug-by-print, which at least seem to keep me more mindful about cleaning up after myself.

[0] http://hackage.haskell.org/package/base-4.12.0.0/docs/Debug-...

I got used to this in Haskell, but I have to say that Elm's solution is pretty elegant:

  Elm:               Haskell:

  f <| g <| x   ==   f $ g  $  x    -- application

  f << g        ==   f . g          -- composition

  x |> g |> f   ==   x & g  &  f    -- reverse application

       g >> f   ==       g >>> f    -- reverse composition
It's nice to be able to change between composition and application by changing between | and < in the operator. I've actually done this quite a lot when refactoring code in Elm lately, there's something visual and intutive about it that I like. It makes it easier to mix left-to-right and right-to-left in the same code, while keeping it obvious what's going on.

I've found that certain parts, that scan/read better as imperative steps (think chains of List.map, Maybe.map, <MonadlikeModule>.andThen etc.) often benefit from a reverse application and/or composition. Sort of to simulate the look of do notation since Elm doesn't have that. Smaller functional parts are better with regular application/composition. With the directional operators, this becomes quite ergonomical.

Probaly helps that I use (and enjoy, ymmv) a font with ligatures for these symbols.

His point about `Either a b` was perhaps further from the mark. It is not a data type that represent logical disjunction. That's what the logical connective, disjunction, is for. Haskell doesn't have union types to my knowledge. Either is not a connective. It's a BiFunctor. His point that it's not "associative" or "communtative" or what-have-you simply doesn't make sense. In fact he calls Either "malarky" or, charitably, a "misnomer."

I don't agree with Hickey, but isn't there a connection between Either as a basic sum type and logical disjunction via the curry-howard correspondence?

And wouldn't "forall a b. Either a b" be the bifunctor, since it has a product type/product category as it's domain, while the result "Either X Y" (where X and Y are concrete types, not type variables) has the semantics of logical disjunction ie. it represents a type that is of type X or type Y?

Yeah, you're right, I got confused when interpreting the parent comment. Thanks for pointing it out!

I guess I overlooked it because the other way is so logically trivial, since it basically boils down to A -> B => (A || Nothing) -> B, which is just an or-introduction. So if you wanna implement Maybe generically the "work" lies on the other side.

But since Hickey's argument sort of is that we shouldn't implement Maybe generically, I guess my argument here becomes circular. (Begging the question maybe?)

Why is it illogical to say that a Maybe a -> b should be callable as if it were a -> b?

Fundamentally because it would require you to conjure up a value of type b from nowhere when the Maybe a is Nothing. If we view the function type as implication this would not be valid logically without some way of introducing that value of type b.

You could imagine some function from Nothing -> b that could rescue us. But since it only handles one case of the Maybe type, it is partial (meaning it could give undefined as an answer). There is basically two total functions that we could change it to:

   * Maybe a -> b in which case we are back where we started.
   *  Unit -> b which essentially is just b, which can be summed up as meaning we need some kind of default value to be available at all times.
So to be able to call Maybe a -> b as a -> b you would need some default value available at all the call sites for a -> b

Now this is only "illogical" because we don't postulate a value of type b to be used as this default.

It's like saying (+) works on integers, but not on 3

No, it's like saying (+) must work on all integers AND a special value nil that is not like any other integers, but somehow included in them and all other data types. We can't do anything with this nil value since it doesn't carry any data, so in the case of (+) we would essentially have to treat it as an identity element.

This is good though, since (+) has 0 as an identity element, so we can just treat nil as a 0 when we encounter (+). However, when we want to define multiplication we still need to treat nil as an identity element (since it still doesnt carry any data), except the identity element for multiplication is 1. This would be repeated for every new function that deals with integers.

So by mashing together Maybe and Integer we have managed to get a frankenstein data type with an extra element nil which sometimes means 0 and sometimes means 1.

Why not just decompose them into Maybe and Integer and supply the default argument with a simple convertion function like fromMaybe?

(FWIW, I actually agree with Hickey that using Maybe in api design is problematic and I've encountered what he's talking about. But while that might be an argument for where he wants to take Clojure, it's not an argument for dismissing type theory the way he does.)

Then they decided to stop improving IE6 (outside security updates) because they (correctly) realized that the web was a threat to the company. IE7 was released 5 years later, literally due to the pressure of other browsers on web standards support.

That's when IE 6 sucked, and we all had to live with that suckiness for years on end because of microsofts insane backwards compatibility policies keeping deprecated software alive on life support.

Even Microsoft has a IE6 countdown timer now https://developer.microsoft.com/en-us/microsoft-edge/ie6coun...

IE6 was an unfathomably damaging disaster for the web.

The damage done to human culture by our current copyright enforcement regime is heartbreaking.

We have to means to make human cultural output accessible to unprecedented numbers of people around the world, but we don't, largely because it would interrupt the flow of money to incumbent rent-seekers.

Makes me think that we've already made the same mistake with fossil fuels and climate change :/ (Tangible benefit: material wealth and prosperity. Theoretically valuable: a balanced ecosystem)

(not to start an off-topic discussion about that, but just an observed parallell)

How so? I've always felt that intuitionism/constructivism were closer to theoretical computer science due to constructivism requiring you to produce (or compute) the what you're trying to prove.

Programming Quotes 8 years ago

A (data) type is a set, and a value is an element in that set.

Nope, a type is not a set. Type theories are separate formal systems designed to be (among other things) computationally aware alternatives to set theory, and they are defined by "terms", "types" and "rewrite rules". The whole point of a dependent type theory is to be able to encode things like sets and propositions in the same formal system.

The rest of your comment is wildly off based on this misunderstanding.

What the GP was probably referring to is the persepctive of dependent types from the lambda cube, where you have three different extensions to simply typed lambda calculus's regular functions from value to value:

    1. Parametric polymorphism, which are functions from type to value
    2. Type operators, which are functions from type to type
    3. Dependent types which are fubnction from *value to type*
Since dependent types allows functions from value to type, it kinda erases the artificial barrier between type-level and value-level. In reality it doesn't really erase it, somuch as replace it with an infinte hierarchy of levels to avoid Girrard's paradox.

Also, I would love to hear which language(s) has a more flexible syntax than Lisp, seeing as expressiveness is one of Lisp's strong points.

I'm thinking about ML-like languages such as Elm, Haskell and Agda (to give a spectrum of examples with varying degrees of complexity in terms of language features).

Lisps surrender the issue of syntax completely

I think I am referring to the same thing as you are when you say that they're not used like other languages, and that they are managed.

This human finds them easy enough to parse

Easy enough is good, but I think we can do better!

It's not that they're liked for their own sake, but they enable many advantages.

I think this is what I was trying to get at. :)

FWIW, I like lips a lot, and consider them far better than most mainstream languages.

That is a naive fallacy

No, I'm not claiming an essential difference, merly that they are optimized for different things.

There is no such thing in existence.

Of course there is, machine code is designed for computers, higher level languges are designed for humans.

Popularity and lack thereof isn't trivially driven by cosmetics.

I'm not talking about what drives popularity, but what drives cosmetics.

Presumably, this is because you've looked at Lisp code, but you probably haven't understood Lisp.

I'm not sure this is a great presumption. Someone can be familiar with lisps, understand the design choices involved, even deeply appretiate the beauty of the resulting language, while still prefering languages with a more flexible syntax

This is what people refer to when they mean that "the brackets disappear after a while." You're not focusing on them; they're handled by something else.

This is exactly the problem. Lisps surrender the issue of syntax completely to the interpreter/compiler, which makes it easy for a machine to parse, but harder for a human. I personally prefer syntax to be designed for humans to read and write, because its going to be translated to something different to be executed anyway.

Now, the simplicity of the lisp syntax is of course intimately connected to the homoiconicity of the language and the extremely powerful macros, so I do understand the value of it and the tradeoffs involved. (Although I find it slightly ironic how it's venerated, given that the original lisp actually had two syntaxes, M-expressions for the human to manipulate and S-expressions for the machine to manipulate).

I personally prefer to do my "meta-programming" in the type-system and have flexible syntax to express functions in different ways relevant to the uses of the language. That can be Elm with it's simple type system and elegant operators for application (|> and <|) and composition (>> and <<) which indicate direction, or it can be Agda with it's unicode syntax and custom mixfix operators, where you can define a if-then-else as a function as "if_then_else_ : (b : Bool) -> (x : a) -> (y : a) -> a" and call it "if <b> then <x> else <y>". I find that the way these languages use operators, reduces the need for parenthesis and allows you to express what your code is doing, whether it's a pipeline of functions or some imperative-like steps being done in order. The result is a syntax that I find clearer.

I am however, well aware that a lot of people dislike Haskell's use of operators and that these languages are a minority in terms of usage indicating that my preference might not be common, but I don't necessarily presume that it's because they don't understand Haskell.

I feel like it's hard to give a concrete practical code example, but maybe I can describe my practical experience.

The main difference I've experienced is that while java's type system forces you to be explicit about your types in a lot of places – like implementing interfaces, assigning types to variable declarations, and just in general the fact that "type inference" is limited to expressions – you get very poor checking of those types, since a lot of their complexity is hidden in the object hierarchy. This in turn, is due to java's orgins as a object oriented language relying on subtyping and inheritance for polymorphism (generics improved somewhat on this.) The result is that a lot of the complexity that haskell gets accused of, ends up in complex design patterns and/or object oriented design principles in java. This has gotten better as java has loosened some of its initial restrictions (e.g. java 8 allowing first-class functions.)

Haskell on the other hand was designed with parametric polymorphism (generics in java terms) in mind and as a functional programming language with first-class functions and no object orientation, allowing it to reap more of the benefits from the more theoretical research into type theory and category theory (yes, this gets us into monads, but I don't think they are nearly as mysterious as the internet makes them out to be.) In my opinion this has made the abstractions in haskell a lot more sturdy and more importantly statically checkable, compared to the object-oriented design principles underpinning a lot of design patterns in java. Haskell also has global type inference, which means you can avoid explicit types in many cases, especially when prototyping small, pure functions. This benefit is somewhat lessened by the fact that haskell's error messages can get very complex, but I subjectively believe that this is due to the fact the type system is checking a lot more and a lot more of what gets checked doesn't have to be given an explicit type by the programmer.

Given this I pretty clearly prefer haskell, but I've spent most of my career writing java and php. In fact, most of my criticism of java drove me to dynamic languages at first (although I would have preferred python over php.) What got me interested in haskell was the fact that I felt that there should be a way to have the freedom and speed of developing in a dynamic languages, while having the same (or better) guarantees of a statically typed language. This what led me to read about type inference.

Now, the complexeties of haskell (or even worse, haskell with ghc extensions) hardly makes my dream a reality, but I still feel like I write a lot less types while having the compiler do a lot more work for me. What has made my dream more of a reality is actually elm[0], which has similar theoretical foundations as haskell, but only has has generics (not interfaces/type-classes like java/haskell.) It's a language in which you can just write your code without types like a dynamic language, rapidly iterate on it and then add a type signature when your satisfied with it. (That's almost what I do in haskell as well, but I can run into more complex problems.)

(This characterisation is probably colored by the period I used java most heavily and might be slighty unfair, but I also limited the description of haskell to the most basic features which it has had since the 90s.)

tldr; mmm, delicious global type inference..

[0] http://elm-lang.org

Nim 0.19 8 years ago

Do you have any examples?

In my experience theoretical purity is largely independent of usability, which I find is more a function of the language's expressibility and the available abstractions (and how well those abstractions play together.)

Take Elm, Haskell and Agda/Idris as examples of four programming languages with strong theoretical foundations, that also vary widely in expressibility and usability

Elm is (imho) very usable even when teaching beginners unfamiliar with the ML-style syntax. The main abstraction is parametric polymorphism (generics in oop-terms), no subtyping with inheritance and no typeclass/interface mechanism. The language has a carefully crafted culture to maintain the beginner-friendliness of the language, explicitly at the expense of expressibility (but not usability.) I personally find elm to be an incredibly usable language, specifically because it's both simple and theoretically pure. Less to understand, for more gained reliability. More practically, I often prototype completely without type signatures, relying on type inference to make sure that I'm not doing anything nonsensical. The end result (for me at least) is the speed and ease of a dynamic language, with the reliability of a statically typed language.

Haskell (without GHC extensions) offers more abstractions and is consequently "less usable" in the sense that you need to understand more theory to use things like Higher Kinded Types and whatnot. By adding GHC extensions you can gradually ramp up the expressibility (by adding abstractions,) while at the same time making the language (slightly) more difficult to use. You can get almost all the way to dependent types which brings us to..

Agda and Idris offers an incredibly powerful mechanism for abstraction called dependent types, where you essentially program your type checker as a logic. But they are (somewhat infamously) difficult to use due to having to understand the consequences of having computations at the type-level (and beyond) and consequently at compile time.

If any of those are any less pure than the other it's probably Haskell, and that's more due to the ad hoc nature of the GHC extension system, which allows you to combine extensions in problematic ways that can hide impurity.

They all compile based on some type theory as a model of computation, but those type theories can vary widely in how complex they are to understand, but the level of complexity doesn't make anything more or less pure. All of them also offers some degree of escape hatch from their "theoretical purity"-prison. Elm has its ports for js interop, Haskell has unsafeIO and friends, Agda has FFI to both js and haskell depending on backend, and I'm guessing Idris has some way of doing this as well. In this sense Elm is probably the most pure.

Yeah, I just wanted to show that (GHC) haskell has a way of getting a type with "zero" inhabitants that actually gets caught by the type checker. (I say "zero" since of course my Empty-type above does contain non-terminating programs and (undefined :: a).)

With "forall a. a" I was coming from the persepctive of intuisionistic type theory where the usual parametric polymorphism just get subsumed by Π-types and universal quantification is usually represented with Π-types, so you would have:

         forall a.a       (Universal Quantification)
    <==> Π(a : Type) a    (Π-type)
    <==> (a : Type) -> a  (Agda-notation)
     ==> a -> a           (Π-type as regular function type)
where Type represents any type. That's what I meant by "type-level identity function".