7 ms·
I get paid to write C++ code. I'm pretty good at it. I understand it's normal usage syntax very well. I don't fucking know what a god damn thing means in Haskel
by forrestthewoods 12y ago
I get paid to write C++ code. I'm pretty good at it. I understand it's normal usage syntax very well. I don't fucking know what a god damn thing means in Haskell.
λ 5 + 7
12 :: Num a => a
What the christ? The 12 I get. Got it. The colons? Not sure. I think it's just a dumb separator. Num is type! What the hell is a => a? I have no idea.
In the top they have an example.
primes = sieve[2..]
where sieve (p:xs) =
p : sieve [x | x <- xs, x `mod` p /= 0]
Jesus. Where to begin? First of all I had to consult a dictionary for sieve. I guess primes is a thing (function?) that takes 2 or more things? Not sure. Not sure what 'where' means in this context. Don't have a good guess. Have absolutely no fucking clue what "p:xs" means. Also no idea what "p : sieve" means. I am equally stumped as to what "[x | x <- xs," means. The "x 'mod' p" I can guess! The /= 0 I'm not sure. Maybe "/=" is equivalent to "!=" ?
And here's the thing. I've tried to read a dozen or so tutorials on Haskell and I give up every time because the syntax isn't explain. Please, please for the love god just tell me what your abstract symbols represent!
Edit: http://learnyouahaskell.com/ http://learnyouahaskell.com/ Reading that now. So far it's pretty good.
- okasaki 12y agoI wrote a page that tries to explain the syntax in a way that's appropriate for programmers (who don't need to be shown how the REPL can be used as a calculator and things like that): http://dv.devio.us/a-quick-look-at-haskell.html http://dv.devio.us/a-quick-look-at-haskell.html
- blitzprog 12y agoReading it right now, really great - thanks!
- berdario 12y agoNum a => a is actually (Num a) => (a) that is, Num a means roughly something like template<class a> class INum{ } `a` is an instance of `Num` if you want to write a times2 function(f(x) = x*2), its type would be (with redundant parenthesis added) (Num a) => (a -> a) so, a->a means it's a function that takes an `a` and returns an `a`... and `a` can be any thing that implements Num The idea with haskell is that you heavily rely on polymorphism on the return types, this might make using code a little more awkward, since you have to explicitly say the type that you want to constrain to, but it makes writing generic libraries/APIs a lot easier, you might find this question interesting: http://programmers.stackexchange.com/questions/105662/is-return-type-only-polymorphism-in-haskell-a-good-thing?rq=1 http://programmers.stackexchange.com/questions/105662/is-ret... `[2..]` is the same thing as itertools.count(2) in Python, an infinite sequence of number, starting from 2 sieve something where sieve (p:xs) = yadayada is usually written on multiple lines sieve something where sieve (p:xs) = yadayada if you know that you can define a function with f x = something_with_x it's somewhat obvious that you're defining a function called sieve that takes a (p:xs) so, you're just defining a function and using it (with the [2..]) argument on the same line (p:xs) is destructuring, it basically take a list (`(:)` is used to `cons`truct lists) and assign the first element of it to `p`, and the rest to `xs` (xs is a commonly used name for this in Haskell) so, it's like calling sieve(list); but you can define its signature as something sieve(T head, list<T> rest); (`something` will turn out to be `list<T>`) /= is indeed the same as !=, so (x `mod` p /= 0) is just (x is not a multiple of p) [x | x <- xs, x `mod` p /= 0] is a list comprehension, and is taking all the elements from xs that aren't multiples of p p, as suggested by the letter, is a prime... so you're recursively filtering the elements who are not multiples of p, leaving only the ones who are multiple only of 1 and p themselves (that is, primes) finally, you're concatenating the prime you're currently acting on (p) with the infinite lazy list of all subsequent primes Hope this helps
- deleted 12y ago[deleted]
- dangirsh 12y agoI feel that this is a result of many Haskell users (and, likely, the writer of that example) being more familiar with mathematical notation than the average programmer. With the correct unicode replacements set up in your environment, it's sometimes hard to remember that you're actually writing code! (IHaskell has these by default) For a declarative language like haskell, mathematics is arguably a much saner inspiration for notation than C/C++. I recommend avoiding any temptations to relate the two. When you see "return" or "class" in the code, treat it as a completely new idea.
- hsod 12y agoI agree, especially about the ":: Num a => a" stuff in the tutorial. berdario's comment above is illuminating, but it was pretty confusing and distracting while doing the tutorial. Not to say it shouldn't be there (I'm guessing it's important) but it should be explained. Some acknowledgement would go a long way-- in the first "slide" after you see that, a sentence about the output format seems appropriate.
- agumonkey 12y agoBeing a god in C++ won't give you anything regarding foreign paradigms. Are you familiar with other FP languages such as ML, Miranda or even Lisp ? More than syntax it's the semantics that differs a lot. Laziness, Immutability...
- forrestthewoods 12y agoNot really. No. And that's the problem! You're right that semantics are what make the languages truly different. However it is literally impossible to even begin to understand semantics if you don't know the syntax. That's what is so frustrating. It might as well be written in Kanji. That's how meaningless it is to me and, I believe, most programmers.
- agumonkey 12y agoI know I've been through this in college. To each his own, but I believe Haskell is too much higher order logic for people to start with it. By having deeply strict principles (lazy, immutable, monad, curryfication,...) they managed to craft a language very tiny and very abstract, too abstract as a starter. These principles are very easy ones when they're explicit. I can be shown in scheme to an average coder with no issue. Try to understand haskell (naive not in-place) quicksort http://www.haskell.org/haskellwiki/Introduction#Quicksort_in_Haskell http://www.haskell.org/haskellwiki/Introduction#Quicksort_in...
- pdpi 12y agoThat's the thing though. Because you have a ton of experience with C++, then you'd probably be able to pick up ruby, python quite quickly (if you don't know them already), because despite the superficial syntatic differences, their semantics are, in the grand scheme of things, close enough to those of C++. You'll trip up on some of the subtler differences, but you'll have a relatively easy time interpreting what the differences mean. When you first look at haskell, the underlying semantics are different enough that your prior experience scarcely helps you interpret the different syntax. Cue howls of frustration.
- freyrs3 12y ago
- gamegoblin 12y agoOk I'll take a stab at a super high level crash course: A note! Haskell has weird syntax. But it actually uses this syntax in cool ways, as opposed to some languages that have weird syntax in an effort to be different. Let's consider the basic unit of Haskell programming -- the function! Here is a function which returns its input: f x = x Notice that there are no parens or types or anything. Since functions are used so heavily, polluting them with parens would make your code look uglier than lisp. Now notice that I didn't include a type signature. In haskell, you can omit them if you want. The compiler will figure out the most general type. Think of this as C++11 `auto` keyword on steroids. If I were to include the type signature, it would look like this: f :: a -> a f x = x Let's break down those symbols. `::` just means "is of the type". So `f :: ...` is "function f is of the type..." `a` is a type variable. Think of this like C++ templates! In C++, an equivalent function is template <typename T> T f(T x) { return x; } Where `T` is the same as our `a` in Haskell. In Haskell, it's common to use the first few letters of the alphabet for type variables. The `->` syntax is a bit funny, but it makes sense when you learn about function currying, which I will not explain now because it can be intimidating to some. For now, just think of the type signature as arg1Type -> arg2Type -> ... -> argNType -> ResultType So for our function `f` of type f :: a -> a That means "The function f takes some argument of type `a`, and returns a value of the same type `a`" Now let's consider a more complex function. f x = 2 * x As you can see, this is a doubling function. It takes a value and doubles it. We can imagine that the value it takes can't be any arbitrary type, because not all types can be doubled! So it doesn't make much sense to pass in a string. In Java style, we might say that the type must implement the interface `Multipliable` or something. It turns out, Haskell has a system that is very roughly similar to Java interfaces or in C++, a class with a bunch of virtual methods. Haskell calls these Typeclasses. So there is a typeclass that specifies that types in the typeclass must implement some basic numeric methods, such as +, -, etc. This typeclass is called `Num`. So we can imagine, our doubling function only takes types that implement `Num`. Our type signature reads: f :: Num a => a -> a f x = x * 2 So everything between the :: and the => is typeclass specifications. So in this one, I just specify that type `a` must implement `Num`. Let's say I also required `a` to have the equivalent of a Java `toString` method, I would say: f :: (Show a, Num a) => a -> a So now `a` must have a `toString` style method and must have numeric methods. Now we are ready to approach the `primes` bit. The first foreign looking thing is [2..]. This is an infinite list from 2 to infinity. How can one have an infinite list? The answer is laziness. Whenever I go and ask for the 10000th value, the list must extend itself to that value if it hasn't yet. So the list only grows for as much as you ask for, but in theory it will grow until you run out of memory. The [m..n] syntax is just nice syntactic sugar because it's used so much. [1..10] == [1,2,3,4,5,6,7,8,9,10] [1..] == [1,2,3,4,5,....infinity] So this is what we know so far: primes = sieve [2..] Which means "primes is equal to the function sieve, called on an infinite list from 2 to infinity" Now you ask, well what the hell is the function `sieve`? Haskell has a nice sugar for defining scoped functions within other functions with the `where` keyword. Consider the doubling function. I could instead do: f x = x * (three - one) where three = 3 one = 1 So it just let's you define values that get used in the function body in a convenient place. This is akin to math jargon where people say "blah blah x something blah where x is blah". So `sieve` is a scoped function (cannot be accessed from outside of `primes` that takes a list as an argument and apparently produces the primes that are in that list. How does it do it? We see some weird syntax in the definition of `sieve`: sieve (p:xs) = ... What is this? It's pattern matching. Let me use fake C++ as an example. Consider the recursive factorial function: int fact(int n) { if(n == 0) return 1; return n * fact(n-1); } Now imagine if C++ let you do this instead: int fact(0) { return 1; } int fact(int n) { return n * fact(n-1); } That is, you make a special case function body when the argument is 0. At runtime, if the arg is 0, it uses the special case body, otherwise it falls through to the general body. So in Haskell, we could implement factorial like fact :: Num a => a -> a fact 0 = 1 fact n = n * fact (n-1) Very clean and sexy! But now you say "Yeah, but I could just use an if statement or a switch..." Now we go into the funny syntax and power of that funny syntax! Not only can pattern matching match on values, but it can decompose those values. What do I mean? Again, let me use some fake C++ as an example. Consider the C++ function int f(pair<int, int> my_pair) { int x = my_pair.first; int y = my_pair.second; return x*y; } Ignore that there is no real reason to pull the values out into `x` and `y`. In a more complex function, I'm sure you can understand why it would be tedious to type `my_pair.first` over and over again and we would want to pull it into `x` or something. Now imagine if C++ let us do this: int f(pair<x, y>) { return x*y; } That is, f takes a pair, and in the type declaration we decompose the pair into its first and second parts, assigning them to the variables `x` and `y`. In Haskell, this is trivial. Consider a direct translation of the C++: f :: (Int, Int) -> Int f my_pair = fst my_pair * snd my_pair Now the idiomatic Haskell, with pattern matching decomposition: f :: (Int, Int) -> Int f (x,y) = x*y Slick, right? So what is `sieve (p:xs)` decomposing? In Haskell, the default [] list is a linked list (there are random access arrays in various libraries). That means we can stick a value onto the front of the list in O(1) time. The way we stick a value onto the front of the list is with the `:` operator. So x = [1,2,3] y = 0:x `y` is now [0,1,2,3] Just like we can compose lists with the `:` operator, we can decompose them via pattern matching with it. So consider the function (I'll omit the type signature for brevity) f (p:xs) = print (p, xs) Let's say we give `f` the argument [1,2,3,4]. What call to `:` would we have to make to get this list? [1,2,3,4] == 1:[2,3,4] So when we send [1,2,3,4] into `f`, it gets decomposed into ` 1:[2,3,4] ` where 1 gets put into `p` and [2,3,4] gets put into `xs`. This is a very common idiom in Haskell. Let's say I wanted to make a function that added 1 to every value in a list. Ignoring that there are much cooler/efficient ways to do this in Haskell, let's make a specialized function for it. addOne (x:xs) = x+1 : addOne xs So let's read through that. In our pattern matching decomposition, we pull off the first value of the list and store it in `x`, and we take the rest of the list and store it in `xs`. Now in our function body, we recompose a list with different values. Those values are `x+1` for the first value, and `addOne xs` for the second value. So we add one to the first value, and stick it into the front of the rest of the list after the rest has had 1 added to its values. You might notice now, that there is no bottom-out case for this recursion. So let's add one. addOne [] = [] addOne (x:xs) = x+1 : addOne xs So we pattern match to catch the special case of the empty list, which returns the empty list, and then we decompose the general case as previously explained. So `sieve` is pulling off the first value, storing it in `p`, and taking the rest of the list and storing it in `xs`. It is returning... p : sieve [x | x <- xs, x `mod` p /= 0] So, that is `p` stuck onto the front of... sieve [x | x <- xs, x `mod` p /= 0] And we know that sieve returns all of the primes within its argument, so in English... p stuck onto the front of all the primes in [x | x <- xs, x `mod` p /= 0] So now what the hell is [x | x <- xs, x `mod` p /= 0]? It is a list comprehension. Hackernews said my comment was too long. Read the rest here: http://pastebin.com/UesYMjjJ http://pastebin.com/UesYMjjJ
- VMG 12y ago> I understand it's normal usage syntax very well. You understand C++ syntax because you're familiar with it, not because it is "normal" in any way.
- 72deluxe 12y agoVery true, but helpfully many other languages have a similar syntax (C, Java, JavaScript, even PHP's OOP system looks like Java) so it is easy to move between them. This looks very alien. Is there any language with a similar syntax?
- lmm 12y agoYes, plenty of other functional languages. I have literally never seen a line of Haskell before but I know ML, Scala and Python, so I can read the line pretty easily: primes = sieve[2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0] [2..] is a list starting at 2; "where" is pretty clearly defining the function after using it. So sieve is a function; p:xs is a destructuring assignment or pattern or whatever you call it (I mean, xs clearly isn't a type, so I guess Haskell writes lists as 1 : 2 : 3 : Nil, where Scala or ML would use ::). (Which means this is a function that only works on a nonempty list, which seems a bit sneaky, but we can see that it's never going to be called for an empty one). So sieve(p:xs) returns p:sieve(...), a fairly straightforward recursion. And the ... is fairly obviously a list comprehension I'd write in Python as "[x for x in xs if x % p != 0]". Did I get it right? The language certainly has an excess of symbols - I think the Python way of writing that list comprehension is much clearer - but it's certainly not incomprehensible; the "where" makes perfect sense, and the [] make a good visual distinction. The : form of lists is not the C way, but it's by no means unknown. The destructuring assignment might be unfamiliar, but once you've got that p:xs is a list there's only really one thing it could mean (and even in Python you can write (a, b) = something()). To a non-Haskeller it's kind of surprising that the code doesn't stack overflow from infinite recursion, but again, given that the code works, there's only one thing it could possibly mean. This was much easier than the time when I tried to read some Clojure. Edit: Just to be sure I got it, I rewrote the one-liner in Python. Had to use generators to get the laziness, and had to split it in two lines for the function: def sieve(xs): p = next(xs); yield p; yield from sieve(x for x in xs if x % p != 0) primes = sieve(itertools.count(2)) Is that clearer than the Haskell? Maybe.
- akurilin 12y agoYours isn't an uncommon reaction, don't sweat it. I remember being thoroughly confused by what I was reading back when I started learning Clojure, after years in C# and ruby. At first I thought no normal human being could understand just what the heck was happening amongst all of those parentheses, but eventually it became second nature. Now I've probably written a good 50KLOC in the language and to me it feels semantically dense, but nonetheless very readable. I then remember switching to Haskell and thinking the same thing: "What the heck is this alien-speak mumbo-jumbo, how can anybody understand this gibberish?! Phooey!". Except this time I could actually remember myself saying the same exact thing 2 years earlier about Clojure and knew that if I just persevered, it'd eventually feel perfectly natural. It's been a few months, and it feels more natural now, even though admittedly Haskell has a lot more language constructs than the very basic lisp, so it takes a bit longer to build up that visual pattern recognition.
- thinkr 12y agoOut of curiosity, was there a specific reason you went from Clojure to Haskell or was it more about exploring a new language? I ask because I am currently learning Clojure, but wonder if there is something unique that Haskell offers.
- qubitcoder 12y agoIt's wonderful to see so many comments on Haskell! I began learning Haskell about a year ago. Coming from the world of C/Java/Ruby/Python, venturing into Haskell has been a phenomenal experience. While frustrating in the beginning, the payoff has been worth it. For me personally, I still wake up excited by the language & what’s left to discovery. Aside from its technical merits, there's a certain expressive beauty and power to it. For example, I recently wanted to write a command-line version of 2048 in Haskell. Instead of being bogged down in the minutia of keeping track of array indexes and state variables, it was simply a matter of transposing lists (corresponding to board rotations). The entire game fit onto a single screen of code that reads like English (once you’re accustomed to the syntax - please don’t be scared off by that!) I spent some time learning Clojure. From my limited experience, it’s certainly easier to get up to speed writing working programs with Clojure. The simplicity of LISP syntax is hard to beat. And the fact that it runs atop the JVM makes things like cross-platform GUI programming a breeze compared to Haskell. But while macros are powerful, it really doesn’t compare to the flexibility & composability of Haskell. Haskell provides some powerful abstractions which make building software easier, and these capabilities simply aren’t possible in other languages — check out the Tony Morris videos on monads & monad transformers for more details [1]. He also explores why these abstractions, if they’re indeed so powerful, aren’t currently more prevalent in software engineering. He’s dedicated to changing that. LYAH is a great resource, but it can be a bit verbose at times.For those interested in diving into the language right away, I recommend checking out the University of Virginia CS 1501 Haskell lectures [2]. They start with the basics and gradually build up to more advanced concepts like functors, monoids, monads, etc. They even have a section on category theory at the end. For a great intro to web development with Haskell, see Ryan Trinkle’s talk on creating a link shorter (using the Snap web framework with a PostgreSQL backend). [3] Live demo [4] For a more formal CS-style introduction to Haskell, see the lectures by Prof. Dr. Jürgen Giesl. [5] [1](http://vimeo.com/robmanthey/videos/page:6/sort:alphabetical/ http://vimeo.com/robmanthey/videos/page:6/sort:alphabetical/) [2](http://shuklan.com/haskell/index.html# http://shuklan.com/haskell/index.html#) [3](http://vimeo.com/59109358 http://vimeo.com/59109358) [4](http://memoi.se/ http://memoi.se/) [5](https://www.youtube.com/channel/UC9ZJ-o00b2t79v6er1O-eBQ/videos?flow=list&view=0&sort=dd&live_view=500 https://www.youtube.com/channel/UC9ZJ-o00b2t79v6er1O-eBQ/vid...)
- Trufa 12y agoHonestly, we call them computer language for a reason, to a certain extent, you're saying you are very good at English but you can't for the life of you understand Japanese, all those weird symbols! Takes a while to be familiarized with a language and specially when the paradigm is completely different, not sure if it's a good or a bad thing (this paradigm change), but you'll have to make an initial effort, that's for sure.
- exDM69 12y ago> I guess primes is a thing (function?) that takes 2 or more things? Primes is an infinite list of integers containing all prime numbers. It's not a function, it's a value just like any other list of integers. You obviously can't do something like that in C++ or most other languages. I think that's a pretty good example to demonstrate the difference between a pure and lazy language like Haskell and the more mainstream kind of languages. If you're primarily a C++ programmer, Haskell should feel alien to you. It's a good thing. The differences in syntax are only skin deep, the differences in the semantics is what is interesting.
- dragonwriter 12y ago> Primes is an infinite list of integers containing all prime numbers. It's not a function, it's a value just like any other list of integers. > You obviously can't do something like that in C++ or most other languages. While the syntax is different, you can create a value just like any other iterable sequence that happens to be a generator for the infinite series of integers resulting from filtering another infinite series of integers by a particular function (which depends on the previous values of the series) in many, and possibly most, modern languages. (And the example here -- primes -- is pretty much the canonical example for every language.) Definitely in most of the popular dynamic languages (Ruby, Python, etc.). And plenty of popular static OO languages, too. (Pretty sure both Java and C#, and definitely sure that Scala and other JVM and .NET static languages can.)
- exDM69 12y ago> you can create a value just like any other iterable sequence that happens to be a generator for the infinite series of integers... I know that. Almost every language out there has some kind of generator/iterator protocol for "lazy" sequences. But they're not a list like any other list out there. You can (usually) iterate through them only once. And you can't apply a generator/iterator type of deal to, say, a tree structure or any other non-linear data structure. In Haskell, every value works like that (unless you explicitly tell otherwise). This is what the "primes" example is trying to show.
- ufo 12y agoTo complement some of the great answers here, I would like to point out that in Haskell, instead of objects carrying around a vtable pointer, the typeclass dictionaries get passed arount as extra implicit arguments to the functions. This is important when you have binary operations like `+`. In haskell both operands must be the same type while with OO interfaces the operands can have different concrete implementations.
- matdes 12y ago" λ 5 + 7 12 :: Num a => a What the christ? The 12 I get. Got it. The colons? Not sure. I think it's just a dumb separator. Num is type! What the hell is a => a? I have no idea." :: means "the stuff after this is the type signature" Normally, a type signature can be as simple as something like 'x' :: Char which is the type signature of the character 'x'. Looking at the type signature of 12 shows two parts "Num a =>" and "a". This can be read as "it returns a generic type `a` that must be an instance of the type class Num", which sounds really complicated but isn't. The "Num a =>" is a type class constraint on the returned type "a". Type classes are basically like interfaces, they set up constraints and methods that need to be implemented for that type, analogous to how in other languages a class can implement an interface. For example, the Eq type class mandates you define the (==) and (/=) methods for that type, analogous to how something like the interface Comparable in Java requires you to define the compareTo method. Thus the concrete type "Integer" is an instance of type class "Eq" because it implements those (==) and (/=) Num is an example of a type class, just like Eq. But Num requires you to define a few more methods class Num a where (+) :: a -> a -> a (*) :: a -> a -> a (-) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a So all of the things you think of as "numbers" are all types that implement "Num", e.g. an Integer, a Float, a Double. So going back, "12 :: Num a => a" means 12 can be any type which implements Num, which is really just a fancy way of saying "this number can be cast into any numeric type". You can perform this casting manually! λ (5 + 7) :: Integer 12:: Integer λ (5 + 7) :: Float 12.0:: Float