As a non-Haskell-er, this article is impossible to follow. I have tried to learn Haskell a few times, always unsuccessfully. Haskell code is filled with so many Haskell-specific things. I realize the goal isn't to be an 'easy' language, but jeez.
Aside from the horrendous variable naming, I'm having trouble understanding what the author actually accomplished, and why it's necessary for a tool like this to exist in the first place.
Can anyone translate the problem into a more simplified, non-Haskell-specific version?
> jonk :: (a -> b) -> ((a -> Int) -> Int) -> ((b -> Int) -> Int)
I have no idea what this line means.
To me, it looks like he wants to write a transformer that matches a specific signature, and he uses a tool that tells him which functions can fit the 'hole' he has in the code, based on types. If I'm correct (I'm assuming I'm not), how is this different than intellisense in an IDE that tells you a list of type-valid functions/variables? Why is this a necessary part of the Haskell coding process? My first instinct is that this demonstrates a problem with the language, if something like this is necessary.
Appreciate anyone that tries to explain. I know most of my assumptions are probably wrong.
> I have tried to learn Haskell a few times, always unsuccessfully
I suspect that people wanting to learn Haskell actually want to learn a statically typed functional programming language. And if you also want that language suitable to everyday problems, the kind of things you are dealing with at work, then Haskell is probably not the best one to start with.
I love how Scott Wlaschin presents statically typed functional programming languages as straightforward and pragmatic solutions to everyday problems:
Once you're comfortable with that (and the way it is presented here really isn't very complex) then Haskell becomes a lot easier to understand.
But Haskell is very distracting because it is much more expressive and complex than than ML dialects for example, so it will be a big learning curve before someone is comfortable reading someone else's Haskell code. For other statically typed functional programming languages (f.i. OCaml/ReasonML, F#, Elm) that is not the case.
Any time you see a '::', it means 'the thing on the left hand side has the type on the right hand side'. As such, you should parse this line as 'there is a function jonk that has a type '(a -> b) -> ((a -> Int) -> Int) -> ((b -> Int) -> Int)'.
A simple type in Haskell is `Int -> Bool`. This means a function that takes a value of type Int and return a value of type Bool.
Now `Int -> (Int -> Bool)`, means 'a function that takes a value of type Int and returns a function that takes a value of type Int and returns a value of type Bool'. And because currying, this can be also read as 'a function that takes a value of type Int, then a value of type Int, and returns a value of type Bool'. As in, if you first call the function with Int, you'll be getting an `Int -> Bool` in return. Then you call it again with Int, and then you get a Bool. Because of how we define precedence of things, this signature can also be written as `Int -> Int -> Bool`, which makes it a bit more readable. However, if you really want to grok Haskell you need to grok this equivalency early on.
Compare this to `(Int -> Int) -> Bool`. This means 'a function that takes a function that takes a type Int and returns a type Int, and returns a type Bool'. By this point you should see that the type is more readable than my human language descriptions.
One last thing you're missing is the types 'a' and 'b'. You'll notice that these start with a small letter, and that's because they are generic types. It means that in practice this function can be called with whatever type instead of 'a' and 'b' in the declaration, it's just that it must be the same type for all cases of 'a' and 'b'. For a simple example, let's look at a function that takes two Ints, a Bool, and returns the first Int if the boolean is true, otherwise the second. The type signature for this in Haskell would be `foo :: Int -> Int -> Bool -> Int`. However, the function doesn't really care that these are Ints - they might as well be any values. As such, we can define the function signature as `foo :: a -> a -> Bool -> a`. This is is naturally similar to generic in C++, Typescript or Rust.
Armed with this knowledge (albeit maybe explained a bit better - see Learn You A Haskell!) you should be able to read this complex type signature now.
class MyJonk implements Jonk<A, B> {
public Summary<B> jonk(Adapter<A, B> adapter, Summary<A> summary) {
// Equivalent to the author's jonk ab aii = _
}
}
Looks like we'll need to create a Summary<B>, so let's do that:
class MyJonk implements Jonk<A, B> {
public Summary<B> jonk(Adapter<A, B> adapter, Summary<A> summary) {
return new Summary<B>() {
public int summarize(Statistic<B> st) {
// Equivalent to jonk ab aii = \bi -> _
}
}
}
}
Following the article, notice that there are a lot of signatures returning int around; pick summary rather than st;
class MyJonk implements Jonk<A, B> {
public Summary<B> jonk(Adapter<A, B> adapter, Summary<A> summary) {
return new Summary<B>() {
public int summarize(Statistic<B> st) {
return summary.summarize( /* ...? */ );
// Equivalent to jonk ab aii = \bi -> aii $ _
}
}
}
}
Skipping some intermediate steps, we end up eventually with the solution which maps to the haskell
// equivalent to jonk ab aii = \bi -> aii $ \a -> bi $ ab $ a
class MyJonk implements Jonk<A, B> {
public Summary<B> jonk(Adapter<A, B> adapter, Summary<A> summary) {
return new Summary<B>() {
public int summarize(Statistic<B> st) {
return summary.summarize(new Statistic<A>() {
public int valueOf(A a) {
return st.valueOf(adapter.adapt(a));
}
});
}
}
}
}
Aside from the horrendous variable naming, I'm having trouble understanding what the author actually accomplished, and why it's necessary for a tool like this to exist in the first place.
Can anyone translate the problem into a more simplified, non-Haskell-specific version?
> jonk :: (a -> b) -> ((a -> Int) -> Int) -> ((b -> Int) -> Int)
I have no idea what this line means.
To me, it looks like he wants to write a transformer that matches a specific signature, and he uses a tool that tells him which functions can fit the 'hole' he has in the code, based on types. If I'm correct (I'm assuming I'm not), how is this different than intellisense in an IDE that tells you a list of type-valid functions/variables? Why is this a necessary part of the Haskell coding process? My first instinct is that this demonstrates a problem with the language, if something like this is necessary.
Appreciate anyone that tries to explain. I know most of my assumptions are probably wrong.