628 karma · joined December 22, 2012
Fixed points are also an important part of the technique described in the paper "Abstracting Definitional Interpreters" https://arxiv.org/pdf/1707.04755.pdf
One of the stories-within-the-story is the original source of Puccini's "Turandot".
It would be like a function that takes
- a Collector that produces an X
- a function that takes an X and returns a Collector that produces an Y
and returns a Collector that produces an Y.
The thing is: while being fed, the result Collector should first feed the initial Collector and, at some time, "switch" to the Collector produced by the function. But when to perform the switch?
> liftA2 :: (x -> y -> z) -> Fold a x -> Fold a y -> Fold a z
The "teeing" functionality is actually one of the "selling points" of the Collector-like library, not a hidden gem:
> This module provides efficient and streaming left folds that you can combine using Applicative style.
It's curious how the same abstraction can have different emphases across languages.
Collectors are also Comonads:
- You can always extract a value of the type that parameterizes the Collector, just by "closing" it.
> extract :: Fold a x -> x
- You could, in theory, "duplicate" a Collector<X> and get a Collector<Collector<X>>. This seems like a dumb function, but it would allow you for example to feed different Streams to the "same" collector, by duplicating it before consuming a Stream, the taking the result Collector, duplicating it again, passing it to another Stream...
> duplicate :: Fold a x -> Fold a (Fold a x)
http://hackage.haskell.org/package/foldl-1.4.11/docs/Control...
> The implementation of arrays is straightforward. The only complication lies with freezeArray, which takes a mutable array and returns a frozen, immutable copy. Often, though, we want to construct an array incrementally, and then freeze it, performing no further mutation on the mutable array. In this case it seems rather a waste to copy the entire array, only to discard the mutable version immediately thereafter.
> The right solution is to do a good enough job in the compiler to spot this special case. What we actually do at the moment is to provide a highly dangerous operation unsafeFreezeArray, whose type is the same as freezeArray, but which works without copying the mutable array. Frankly this is a hack, but since we only expect to use it in one or two critical pieces of the standard library, we couldn't work up enough steam to do the job properly just to handle these few occasions.
It seems that the recent addition of linear types to GHC 9.0 will enable "freeze" operations that are both fast and safe: http://hackage.haskell.org/package/linear-base http://hackage.haskell.org/package/linear-base-0.1.0/docs/Da...
Incidentally, the "Vimium" browser extension also supports those marks with the same syntax; they're an interesting alternative to conventional bookmarks for frequently accessed pages. https://github.com/philc/vimium/blob/master/README.md
We don't really care about those patterns of interaction as long as they end up doing the right thing: allowing the SUT to affect his "outside world" as intended.
Making assertions about the exact pattern of interaction (that is: using mocks) is too high granularity, and will make the tests break on valid refactorings. Because we don't care about the pattern itself—it's just an implementation detail—but about what it achieves.
> One, types are a concretion. If you’re looking for higher level of abstractions to get flexible behaviour, you’re ultimately going to have a world of pain
I don't see why types should be in the way of flexible behaviour. Frameworks like Spring in Java use types to direct dependency injection and it works well.
Also this:
> Types wrap data and treat it like a black box whereas schema describes the shape and content of data.
Types can be made abstract and blackboxy, and sometimes that's what you want. But doesn't a record type give info about its fields? Doesn't a sum type give info about possible alternatives in the values?
> As such haskell ultimately suffers a lot when they have to interact with the real world. Suddenly they are left reeling as they find out that the real world is, in fact, dynamic.
The trick is to know what we are really modelling. If we are deserializing domain objects from JSON, it makes sense to have types for the domain objects. If we are writing a tool like, say, jq, perhaps we should merely have a datatype for the JSON tree itself: http://hackage.haskell.org/package/aeson-1.5.5.1/docs/Data-A...
> Bottom up design is something we’ve learnt collectively as a good way to be much more flexible in responding to change.
Even if you want to design bottom-up, the moment you want to add anything to your program, you need a little top-down thinking, if only at the micro-level. You want to create something new that isn't there, and then think how to accomplish that with the tools you have.
If I'm reading a novel, it would be jarring to read "and then he took 45 steps of average length to reach the kitchen" instead of "he went to the kitchen".
Meanwhile, with a Collector, you can read the inputs with standard IO actions just fine, and feed them as they are produced.
Another difference is that you can combine a "Collector a b" and a "Collector a c" into a "Collector a (b,c)" that, like its components, only requires a single pass of the data. (This would be the Applicative instance for collectors.) Combining functions [a] -> b and [a] -> c doesn't necessarily "stream".
Also, I didn't use GADTs, only GADT "syntax" in which data constructors are provided as functions with signatures (MakeCollector :: ...)
Personally, I find this syntax much clearer for existential types. It amounts to having a type variable in a parameter which doesn't appear in the return type of the constructor.
In Haskell, using GADTSyntax, it would be something like
data Collector a b where
MakeCollector :: (x -> a -> x) -> x -> (x -> b) -> Collector a b
That is: to construct a collector that ingests as and returns a single b, you need to supply a step function, the initial state of type x, and a "tally" function that calculates the result b from the final state. The type of the state ("x") is not present in the type "Collector a b"; it is an "existential".In the Java Collectors framework, this is (roughly) analogous to
Collector.of (Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Collector.Characteristics... characteristics)
And the type parameter that corresponds to the existential in Haskell is: A - the mutable accumulation type of the reduction operation (often hidden as an implementation detail) alloc :: Int -> (DArray a ⊸ ()) ⊸ Array a
According to the linear Haskell paper: "f :: s ⊸ t guarantees that if (f u) is consumed exactly once, then the argument u is consumed exactly once." Also: "To consume a pair exactly once, pattern-match on it, and consume each component exactly once"So, if we call splitDArray inside that callback, then we are obliged to consume each of the resulting (DArray a, DArray a) exactly once to satisfy the constraint that the input is consumed exactly once.
- "Objects and Classes, Coalgebraically" (1995) http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.29.6...
- "Codata in action" (2019) https://www.microsoft.com/en-us/research/publication/codata-...
Coalgebras fit very well with the "hidden internal state" that characterizes objects in OOP.
> You cannot believe the view I will defend in this book. I therefore will not be able to convince you that this view is true.
What is a painting from 1475 supposed to tell us about the reading habits of the real St. Jerome, patron saint of librarians?
The paper "A History of Early Christian Libraries from Jesus to Jerome" by Thomas M. Tanner looks like a good source of information on the subject, but sadly it's only available from JSTOR.
From Wikipedia: https://en.wikipedia.org/wiki/Labyrinth
"Although early Cretan coins occasionally exhibit branching (multicursal) patterns,[2] the single-path (unicursal) seven-course "Classical" design without branching or dead ends became associated with the Labyrinth on coins as early as 430 BC,[3] and similar non-branching patterns became widely used as visual representations of the Labyrinth – even though both logic and literary descriptions make it clear that the Minotaur was trapped in a complex branching maze."
If the cretan Labyrinth didn't have branching paths, why would Theseus need a ball of thread to retrace his steps? https://en.wikipedia.org/wiki/Ariadne#Minos_and_Theseus
Speaking of labyrinths, programmers are like Daedalus: they design labyrinths, only to be immured inside them. Daedalus was commissioned to build an actual labyrinth; programmers set out to build cathedrals, but then mess up. They keep reworking sections of the labyrinth even as they are lost inside, trying to straighten out the paths, render them comprehensible. But they always end up creating more twists and turns.
Also, in German, earlier software versions are located downwards, not backwards: "Abwärtskompatibilität"
Another masterful writer of surreal science fiction stories was R. A. Lafferty https://www.amazon.com/Best-R-Lafferty/dp/1473213444/