HNHacker News
TopNewBestAskShowJobs

pka

1,132 karma · joined January 2, 2012

submissionscomments
pka··on Does OO really match the way we think (1997) [pdf]
Apart from this being the prime example for the argument for immutable data (i.e. just keep the whole state of your editor at every undo point somewhere; restore to previous states when undoing) the "OO way" doesn't even need OO features, i.e. see typeclasses or traits.
pka··on Why We Chose Typescript
Check out Purescript [0]. I've used it in production.

[0] http://www.purescript.org

pka··on Snek: An experimental system for writing generative art systems
> cloning the entire structure for every applied function sounds like it will be slow? is that not what you will have to do in practice?

In short, no - as an example, when an element is added to an immutable map, a big part of the newly produced map is structurally shared with the old one (check out [0].)

As for the ordering, you could have a list of geometry -> geometry functions. You could then take the initial geometry and apply the list of functions in order, or a permutation of it, or drop some functions, or apply some others multiple times, and so on.

But anyway, it's just an idea - thinking in terms of atomic geometry transformations instead of alternations may or may not be easier in more complex scenarios, I don't know :)

[0] http://hypirion.com/musings/understanding-persistent-vector-...

pka··on Snek: An experimental system for writing generative art systems
It's indeed very interesting!

What I meant is that it might be simpler to just have pure functions operating on immutable data structures, which you could then compose in whichever you want. I.e. you could have a function collapse-vertices that takes some geometry and produces some new geometry (without altering the initial one!), which you could then compose with some other pure function, like produce-random-vertices, and so on, much like a Unix pipe.

A free monad is just a formalization of the command/interpreter pattern in a purely functional context (i.e. most useful in languages like Haskell or Clojure).

pka··on Snek: An experimental system for writing generative art systems
So if I'm understanding this correctly, you are generating a data structure describing geometry manipulations which can then be applied to a distinct geometry "instance?" If so, isn't this just a free monad?

Also, what are the advantages of this approach over simple function composition operating on immutable data structures/geometries?

pka··on Imperative Haskell
Then every CPU instruction is just (Universe -> Universe) :D
pka··on Why Elm? [pdf]
For the curious, [0].

[0] https://github.com/zrho/purescript-optic-ui

pka··on I tried Haskell for 5 years
Ha, I guess this must be some kind of selective blindness then :) I've seen myThreadId many times, it makes perfect sense, and yet, whenever I've had to grab the spawned thread id I've always reached for mfix... Well, in this particular case there's of course no need for that.

But the general pattern applies to many things. One other example that immediately springs to mind is e.g. registering a callback while needing the ability to unregister the callback from within itself, using some sort of id returned from the registering function.

pka··on I tried Haskell for 5 years
Here's a practical application of mfix:

    mfix $ \threadId -> forkIO $ do
      -- computation in forked thread
forkIO creates a new thread and returns its thread id. However, in order to access the returned thread id inside the forked computation, normally one would have to store it in a variable and then read from it inside the thread.

mfix, by nature of its laziness, captures the return value of the function passed to it, and passes it to said function.

pka··on Why ML/OCaml are good for writing compilers (1998)
https://www.haskell.org/hoogle :)
pka··on Programmer Competency Matrix (2016)
I'd be interested in continuing this discussion via mail. You don't have a public mail in your profile, so here's mine if you are too: p.kamenarsky@gmail.com
pka··on Programmer Competency Matrix (2016)
> You can create an object with the right members and pass it in to a function expecting an interface without even explicitly identifying it as "implementing" the interface.

Row polymorphism is indeed very useful. Purescript (a Haskell descendant compiling to js) has it and I wish Haskell did too.

> The point of using OO in a giant complex program is the isolation you get, so that a team of 1000 people can develop the app in a reasonable way.

> Not everything is easily isolated: Sometimes you need a control over here to poke into the state of a control over there, and you don't want that information in some "god object" at the top level; you want a direct connection and mutability.

The above two statements directly contradict each other. Yes, OOP preaches isolation and programming against abstract interfaces, in theory. In practice, more often than not one is tempted to do something the easy way and "poke into the state" of another object. This has disastrous consequences: you never know what will happen where when you call a method. Everything is fair game. It's like changing the lock of the basement door in a skyscraper somewhere only to have it fall because the steal beams depended on this exact lock to hold some state, since it was "faster that way". It's insane.

I've seen so many OOP clusterfucks that I seriously can't believe that you haven't in your much longer career.

Sometimes FP is not a good idea - mostly when performance, memory or deterministic runtime behaviour is a must. But even there languages like Rust show that it may be possible to have one's cake and eat it too.

Other than that, I'd be genuinely interested in hearing what kind of problems you think FP wouldn't work well for.

> Which is why it's so popular for solving complex problems?

It is, actually. I've worked on a very complex codebase in Haskell at my last job (a pathfinding and ticket price calculation backend with ridiculous specifications full of special cases and exceptions that changed almost every day) and I was able to be productive almost from day one. We could refactor with almost absolute confidence because the language is pure and type safe, and we had to because the specs changed so quickly.

Facebook's spam filter and Jane Street's OCaml stack come to mind as other examples.

There aren't as many applications written in FP languages simply because these languages aren't that popular. But popularity isn't a metric of quality.

> If you're putting more cognitive load on the developer, then you're just changing the problems, not preventing them.

I absolutely find the opposite to be the case. Haskell reduces the cognitive load on the programmer, because it is pure (i.e. no need to keeping track of dozens of interactions at once) and type safe (i.e. a function marked as pure can't call a function with IO).

Not to mention things like an actual working software transactional memory implementation that makes concurrent programming as easy as writing a single threaded program. No other language, with the notable exception of maybe Clojure, has a practical STM library. But anyway, STM is just a bonus.

In short, I would say that FP actually delivers what OOP set out to but couldn't. Many FP ideas that were almost unheard of just a couple years ago, like immutable data, pure functions, composition over inheritance, non-nullable types, algebraic data types and so on are slowly but surely taking hold in the mainstream. Pure FP languages just take it to the next level :)

pka··on Programmer Competency Matrix (2016)
> For the entire app? You'd end up with a "god object" that would make the app unmaintainable. OO gives you compartmentalization that you don't get with FP.

Well, not quite; take a look at the Elm architecture [0]. It's basically what Redux gives you, but more explicit.

The idea is, that yes, while you have a god object in the top-most component, child components don't know anything about it; they operate on their piece of state completely independently. This makes UI elements perfectly composable and reusable, since in the end, a UI element is just a pure function and nothing more.

Not to mention other nice side-effects (sorry :)) of pure UI architectures, like time-travelling debugging, sane logging and so on.

> FP is great for problems up to a certain complexity. After that it falls apart.

I disagree completely. FP, and moreover pure FP, is especially well suited for complex problems. Pure functions are testable (if you haven't heard of QuickCheck / generative testing, you've missed out) and as I said above, composable.

If anything, I've never seen a big OO program made out of truly reusable components. Because classes and methods are often impure, before calling a method one must make sure that the correct environment is set up in the right way (if you've ever forgotten calling initSubsystem() before subsytemDoSomething() you know what I mean).

One can rip out a pure function from one codebase and reuse it, as-is, no modification necessary, in another one without any problems. How often does this happen in a big OO project?

> Is "instance" adding an interface to TileMap/HexMap so that either can be passed in as a parameter that requires a RectIterable? Can other interfaces be added that way to TileMap/HexMap?

Yes, and yes. In Java, when you have a class like this:

    class A implements B, C {
    ...
    }
in some library, you can't add D to the list of implemented interfaces.

In Haskell, you can:

    instance D A where
      ...
Haskell assumes an "open world" - i.e. everybody can add instances to a data type even after it has been defined already.

I can't give you a complete overview of the differences between ad-hoc and parametric polymorphism, but there are plenty of resources online if you are interested.

> In my editor, can I say something like myTileMap.iterateRect after doing this, or do I need to know that iterateRect exists and remember exactly what it's called?

I can only speak for Haskell here. It's a static language, so things like autocompletion, show type at cursor, jump to definition etc are not a problem. The tooling could be a whole lot better, no doubt, but it's not bad. The REPL helps a lot, too.

[0] https://guide.elm-lang.org/architecture/

pka··on The Law of Conservation of Complexity
To my very limited understanding, no.

Basic example, puring cream into a cup of coffee. At first low entropy, low complexity (cream at top, coffee at bottom). Then higher entropy, high complexity (cream and coffee mix according to laws of physics, different concentrations at different locations in the cup, streams and such). At last, highest entropy but again low complexity (everything is homogenous).

pka··on Programmer Competency Matrix (2016)
I think the point still stands: FP is different.

None of the things you listed are particularly hard in an FP context. Classes/traits and parametric polymorphism will allow you to do these things just fine (Haskell code):

    class RectIterable map where
      iterateRect :: map -> (Point -> a) -> Rectangle -> [a]

    // define map data structures
    data TileMap = ...
    data HexMap  = ...

    // make them instances of the RectIterable class
    instance RectIterable TileMap where
      iterateRect = ...

    instance RectIterable HexMap where
      iterateRect = ...
> Object oriented programming ... is good for UI

I don't know, but React begs to differ. A pure function (state -> UI) is basically one of the best UI programming paradigms we've come up with so far, the other probably being FRP.

pka··on Programmer Competency Matrix (2016)
Inheritance is orthogonal to that. I.e. Erlang, an (impure) functional language without inheritance was literally built for that sort of thing.
pka··on Haskell Concepts in One Sentence
Maintaining state was just one example. See my other comment [0] for others.

Regardless, you can't maintain state with closures in a pure language. You could close over some local "variable", but that variable would be immutable. In an impure language that would work, but then you're back to managing global state, even if that state is not defined at the top level (i.e. global in the sense that different closures could still have access to the same piece of state, concurrently).

[0] https://news.ycombinator.com/item?id=13965917

EDIT: just to clarify, monads manage state purely. I.e, instead of writing:

    function statefulFunction(state) {
      (a, state1) = f(state);
      (b, state2) = g(a, state1);
      (c, state3) = h(b, state2);

      return (c, state3);
    }
you can write:

    statefulFunction = do
      a <- f
      b <- g a
      c <- h b
      return c
But the resulting function is still pure, i.e. (state) => (c, state).
pka··on Programmer Competency Matrix (2016)
But OOP without inheritance is what FP gives you anyway:

    data Address = Address
      { city   :: String
      , street :: String
      }

    data Person = Person
      { name    :: String
      , address :: Address
      }
My general impression of recent-ish OOP developments is that people have started preferring composition over inheritance anyway, for good reasons. I guess I should've framed by question more like "the merits of inheritance over composition".
pka··on Haskell Concepts in One Sentence
Compare again:

    bind(StatefulFunction<a>, a => StatefulFunction<b>) => StatefulFunction<b>
    flatMap(List<a>, a => List<b>) => List<b>
Look at the structure. Don't pay attention to what List or StatefulFunction actually are, that's not important here. The thing to focus on is that if you can provide this kind of general function for something, then that something is probably a monad.

I.e. a function that takes in Something<a> and a function from a to Something<b> and produces Something<b>.

So why is this pattern so useful that everybody freaks out about it?

Well, that syntax sugar I showed you at the end, let's look at it again:

    do
      a <- f
      b <- g a
      h b
The general idea here is that "a <- f" is translated into bind(f, a => ...). The whole block would turn into

    bind(f, a => bind(g(a), b => h(b)));
Bear with me. Now, let's look at one more example:

    bind(Optional<a>, a => Optional<b>) => Optional<b>
Optional<a> is just a data type that maybe contains an <a>. Think of it as a safe replacement for null. So, bind for Optional looks at the first argumenta and passes it on to the function and returns its result, if the Optional is not empty. If it is, bind also returns an empty Optional.

Now let' say we have a bunch of Optional producing functions:

    getUser(userId)    => Optional<User>      // user may not exist
    getAddress(User)   => Optional<Address>   // user may not have entered an address
    getStreet(Address) => Optional<Street>    // address may be weird
Now ok, what would we do in a language without monads?

   user = getUser(userId);

   if (user.hasValue()) {
     address = getAddress(user.getValue());

     if (address.hasValue()) {
       street = getStreet(address);

       // do something
     }
  }
Well, that's not great. But because Optional is a monad and we have that nifty bind sugar, that's how that code would look like in Haskell:

    do
      user    <- getUser userId
      address <- getAddress user
      street  <- getStreet address
Much better.

More generally, it turns out monads are literally everywhere. The guy that talked about CPS transformed programs being monads is right - that's the continuation monad.

Let's look at that. If you know node.js, you know that its API is basically asynchronous:

    fs.open(path, callback)
    fs.read(fd, ..., callback)
    fs.close(fs, callback)
Now, let's read some file in node:

    fs.open(path, function(fd) {
      fs.read(fd, ..., function(result) {
        // do something with result

        fs.close(fs, function() {
          // file closed
        }
      }
    }
Well, that's ridiculous. But becase we have the continuation monad, we can write exactly the same thing in Haskell:

    do
      fd <- open path
      result <- read fd

      // do something

      close fd
So why are monads so useful? Because they are so general. They can be used to abstract over many many things that exhibit the basic pattern (S<a>, a => S<b>) => S<b>. Not to mention that when you write a function to generally work with monads, it will work for ANY monad. List, State, Maybe, Cont - doesn't matter.

There's not much more to it than recognizing that pattern. That's it. Once you see that that's literally it you'll start seeing monads everywhere :)

pka··on Programmer Competency Matrix (2016)
Oh, but you can have mutable state in different parts of a program even in a pure FP language (without having to jump through any hoops, it's right there and pretty easy to use), although a processor or signal simulator may benefit from things like FRP that are a lot better suited for, well FP, then imperative languages.

But I actually wasn't asking about pure vs imperative, but rather FP vs OOP. I haven't heard of a pure OOP language, but in that context I think those properties could be seen as more or less orthogonal.

pka··on Programmer Competency Matrix (2016)
Genuinely curious, when would OOP (as opposed to FP) be better suited to a problem domain in your opinion?
pka··on Haskell Concepts in One Sentence
Just to expand a little bit upon the sibling comment.

Let's say you need a function that maintains internal state. Now, in non-pure languages, you could that by simply referring to some global variable, but let's agree that's bad. So how can you model internal state purely?

Simple, your function takes the current state and produces a new state, along with its result. So, a pure function like:

    f(String) => Int
turns into the stateful function:

    f(String) => (State => [Int, State])
I.e. a function that returns a function that takes in a state and produces a result and a new state.

Cool, but now let's say we have another function g:

    g(Int) => (State => [Double, State])
Let's now say we want to compose those two functions:

    let result = g(f("5"));
However, this won't work, becase f produces (State => [Int, State]) but g expects Int.

So, here's bind for the State monad (implementation is not important):

    bind(State => [a, State], a => (State => [b, State])) => (State => [b, State])
If we define a "type alias" (just a synonym, so we don't have to type that much) for a stateful function:

    type StatefulFunction<a> = State => [a, State]
and then replace the above bind definition with it:

    bind(StatefulFunction<a>, a => StatefulFunction<b>) => StatefulFunction<b>
you can see that is has the same structure as flatMap!

    flatMap(List<a>, a => List<b>) => List<b>
With the type alias, f and g would then look like this:

    f(String) => StatefulFunction<Int>
    f(Int)    => StatefulFunction<Double>
So now, here's how you compose f and g:

    bind(f("5"), a => g(a))
In Haskell, there's syntax sugar for bind, so the above turns into

    do
      a <- f "5"
      g a
which is very similar to a conventional language:

    {
       a = f(5);
       g(a);
    }
EDIT: forgot result type in bind and flatMap.
pka··on Alpaca – Functional programming inspired by ML for the Erlang VM
There's also purerl [0], an Erlang backend for PureScript. Would be cool if the two projects could join forces.

[0] - https://github.com/purerl/purescript

pka··on Your yearly dose of is-the-universe-a-simulation
That's intersting.

If the universe could be simulated, you could, in theory, run that simulation on a (very large) piece of paper by hand. Running the simulation would then be equivalent to calculating some numbers and writing them down.

Basically, if I wrote down the correct sequence of numbers, I could simulate you. But is calculating those numbers actually necessary? Is it enough to just have the result of that computation without performing the actual steps to obtain it? If yes, then a random number generator would be all that is needed to simulate our (and infinitely many other) universes.

And if that's the case, maybe it's not such a big leap to imagine that the mere existence of the physical laws as an abstract concept is enough to give rise to a simulation?

pka··on Three Months of Go, from a Haskeller’s perspective (2016)
> There is one thing in Go, that I haven't met anywhere else. I can really understand any codebase I stumble upon in a matter of seconds.

Care to elaborate a bit more?

I understand that when you see for(...) { b[i] = f(a[i]); } then you instinctively know that it's a loop that maps over some sequence, and so on. But let's say you have a 50-100kloc codebase with a 100 files in it. In what unique to Go way does Go help you understand the big picture/coarse architecture of such a project?

pka··on Virtual machine escape fetches $100k at Pwn2Own hacking contest
I'm not promoting Rust, nor have I actually written anything in it. I do think though it's a step in the right direction.

> But you don't get that effect by ramming it down other people's throats, you get that effect by showing it in practice.

I disagree. I've seen many C programmers who think that C is the be all end all of programming languages. Those people will not be convinced by showing them another technology that is better in practice. The fact that it's actually unbelievably hard to write correct software in C is somehow a point of pride for many people, though I speculate that few of them can actually follow through.

Of course there will always be logic bugs in software, even with formal systems and whatnot (i.e. bugs in the specifications). But memory bugs and the resulting security exploits could in many cases be a thing of the past already.

Many industries have similar regulations, i.e. seatbelts in cars. If somebody dies because of a faulty or non-existing seatbelt, there will be consequences. If somebody dies because they were talking on the phone and the car didn't prevent it, well, you can't control everything.

Is this a buffer overflow? Why was this written in C? No good answer - pay up.

pka··on Virtual machine escape fetches $100k at Pwn2Own hacking contest
There will always be software bugs. What I'm trying to say is that people should be made liable for avoidable bugs due to incompetence or ill-placed personal preferences ("Real programmers write in C!11").

There are times when C is unavoidable, unfortunately, but when somebody chooses a technology regardless of better, safer alternatives, "just because", they should be made liable, absolutely.

pka··on Virtual machine escape fetches $100k at Pwn2Own hacking contest
It doesn't make economic sense because VMWare and Microsoft don't have to pay out of pocket if a box in a datacenter is compromised and data is leaked because of bugs in their VMs and kernels that are only possible because of C.

This is like hiring somebody to build a bridge, it falling due to incompetence killing hundreds of people and then having nobody suffer any consequences. "Eh, bridges fall, what can you do."

Make people pay for buffer overflows and C is gone tomorrow.

pka··on Virtual machine escape fetches $100k at Pwn2Own hacking contest
That is definitely why we need to stop using C. It's beyond ridiculous.
pka··on Building Safe AI: A Tutorial on Homomorphically Encrypted Deep Learning
You are implicitly talking about being able to objectively measure a goal's "optimality".

Unless you believe in absolute morality or the like, there's no such thing as an objective measure. A goal can only be optimal to an agent.

In your example, the fact that we pursue science and can destroy ants doesn't mean that their goal is "objectively less optimal". Their goal is absolutely optimal to them, though they can't reach it if it collides with ours.

Some goes for a superintelligent AI.

← PreviousPage 4 of 13Next →