[HN Gopher] Comparison of Haskell and Julia List Comprehensions
___________________________________________________________________
Comparison of Haskell and Julia List Comprehensions
Author : todsacerdoti
Score : 56 points
Date : 2021-07-07 15:41 UTC (7 hours ago)
(HTM) web link (blog.lojic.com)
(TXT) w3m dump (blog.lojic.com)
| moomin wrote:
| I mean, the irony here for me is that I regard Haskell's list
| comprehensions as a bit of a bag on the side. There's not much
| that couldn't be equally readably implemented using other Haskell
| syntax.
| tikhonj wrote:
| One feature I rather like in Haskell is being able to _zip_ with
| list comprehension syntax[1]: l> [(x, y) | x <-
| [1..] | y <- [1..3]] [(1,1),(2,2),(3,3)]
|
| This example is a bit silly--I would just write zip [1..]
| [1..3]--but it makes more complex expressions involving zips much
| easier to read at a glance. I looked through some real code I've
| written, and being able to do things like this is definitely
| nice: toHashMap values = HashMap.fromList
| [ convert name value | name <- Schema.fldName <$>
| Schema.fields t | value <- Vector.toList values
| ]
|
| Of course, this particular example would be nicer if Haskell had
| something like dictionary comprehensions for building hash maps,
| but this isn't an awful substitute, and zipping multiple
| sequences together is useful in a lot of other configurations as
| well!
|
| [1]: I should add that this requires enabling the somewhat
| misleadingly named `ParallelListComp` extension. Frankly, I think
| extensions like this should just be added to the language--this
| introduces new syntax, so it never changes previously valid code
| --but the Haskell community is very conservative about making
| changes to the core, standardized part of the language :/.
| ssivark wrote:
| That actually seems confusing. I would normally expect such
| code to perform a Cartesian product i.e. _all_ pairs of (x,y).
| dwohnitmok wrote:
| It would be a Cartesian product if the second `|` was changed
| to `,` (as is the case in the article and most examples in
| this comment section).
| DNF2 wrote:
| Are there multidimensional comprehensions in Haskell? In Julia
| you have jl> [ (x,y) for x in 1:3, y in 2:5 ]
| 3x4 Matrix{Tuple{Int64, Int64}}: (1, 2) (1, 3) (1, 4)
| (1, 5) (2, 2) (2, 3) (2, 4) (2, 5) (3, 2)
| (3, 3) (3, 4) (3, 5)
| jolmg wrote:
| You can have them if you can define Matrix as a data structure
| with a Monad instance. I don't know if there are any packages
| that do something like that.
| codesections wrote:
| The Raku syntax for list comprehension[0] is very similar as
| well.
|
| Julia: [ x^2 for x = 1:5 ]
|
| Raku: do $_2 for 1..5;
|
| [0]: https://docs.raku.org/language/py-
| nutshell#List_comprehensio...
| maweki wrote:
| Is Julia eager? In Haskell you can also do infinite lists
| [1..]
|
| This generates all the integers. The first example
| [ x^2 | x <- [1..5] ]
|
| Is basically already list comprehension within list
| comprehension. Are infinite lists possible?
| bobbylarrybobby wrote:
| Julia's Iterators package has infinite iterators but I don't
| think there's syntax for them
| oscardssmith wrote:
| If you use parens instead of brackets, you get lazy
| comprehensions in Julia.
| dwohnitmok wrote:
| Haskell's comprehension syntax is very powerful and quite a bit
| more general than what is described here.
|
| It can both be generalized to arbitrary monads through
| `MonadComprehension` and has extended syntax that brings in basic
| SQL operators (such as grouping and sorting) through
| `TransformListComp` as well as zipping through
| `ParallelListComp`.
|
| The links are respectively
| https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/mona...,
| https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/gene...,
| and
| https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/para....
|
| And what's more is that all three of these play very nicely with
| each other, so that you can write SQL-like syntax to deal with
| say an arbitrary, side-effectful stream rather than a finite data
| structure.
|
| That means you can write something like (to take the example from
| the `TransformListComp` docs) output = [ (the
| dept, sum salary) | (name, dept, salary) <-
| employees , then group by dept using groupWith
| , then sortWith by (sum salary) , then take 5 ]
|
| where `employees` is e.g. a paginated HTTP endpoint that you're
| hitting.
|
| It can make for some very nice code.
| mapgrep wrote:
| This sounds something like transducers in Clojure, I'd be
| curious to see how they compare! Fascinating to see similar
| ideas emerging in various places. (It's possible Clojure
| borrowed some concepts here as I know Rich Hickey often
| discusses how Haskell has solved a particular problem when
| discussing a Clojure approach.)
| dwohnitmok wrote:
| This is different from transducers in Clojure (note how
| `dept` and `salary` show up in multiple levels despite not
| being manually threaded through). This is much more similar
| to the way SQL syntax works.
|
| Transducers in Haskell (and really in any language, including
| in Clojure) are functions of the form `a -> List b` (yes this
| is a fully polymorphic transducer despite the use of `List`;
| it can be applied to concrete data structures as well as e.g.
| streams of the form found in core.async, I can explain more
| if you're interested).
|
| I actually think the design of transducers is a bit of a wart
| in Clojure. Clojure usually goes for the least powerful
| abstraction (so e.g. data < higher-order functions < macros),
| but for some reason when designing transducers decided to
| move away from the simple `List` based approach and instead
| do a Church encoding of the list to end up with a higher-
| order function. I wonder if it was performance related
| (although I'd be slightly surprised to see a higher-order
| function performing better)?
|
| One of these days I hope to see a library expressing the
| alternative form of transducers since I find those much much
| easier to understand and write from scratch without pre-
| existing transducer generating functions.
| mbauman wrote:
| Conversely, Julia's comprehension syntax is very powerful and
| quite a bit more general than what is described here -- but in
| a different way.
|
| - They're not _list_ comprehensions, they 're one-dimensional
| _vector_ comprehensions. And they seamlessly extend to
| multidimensional _array_ comprehensions.
|
| - Just drop the square brackets and instead of a dense array
| you get a lazy generator!
| anyfoo wrote:
| Correct me if I'm wrong, but doesn't them being Monad
| comprehensions in Haskell automatically mean that Haskell's
| "list" comprehensions can be all of those things as well, and
| more? As well as the language's laziness by default that
| pretty much encompasses the generator aspect in general
| anyways.
|
| (Although I'm not really trying to pit anything against each
| other here, they are different languages with different
| requirements.)
| moonchild wrote:
| APL has no list comprehensions, but can generally be as clear and
| concise (if not more so) using ordinary array operations. Re-
| implemented: 2*[?][?]5 1 2 3 [?]., 4 5
| ([?],"[?]+[?][?]1+3-[?])"1 2 3 [?]ok, this one is not so hot,
| but apl has no primitive for x..y concat - [?]
| [?]primitive firsts - [?][?]-1 [?]leading-axis
| version firsts - [?][?]1 [?]trailing-axis version
| firsts - [?]" [?]nested version length - +/1[?]"
| [?]this counts! factors - [?]0=[?]|[?] [?]doesn't
| include the number itself prime - (,1) [?] factors
| primes - (prime[?]0{[?]/[?]}[?])[?] [?]{[?]/[?]} is a historical
| artefact, please ignore it find -
| (1[?][?]){[?]/[?]}[?]9=0[?][?] [?]{[?]/[?]} is very sorry for
| its repeat appearance pairs - 2 ,/ [?] sorted - [?]/
| 2<=/[?] positions - [?]= is_lower - {sorted [?]ucs
| 'a'[?]'z'} lowers - +/(is_lower[?]0) count - +/=
|
| Note many of these are polymorphic wrt rank (or can trivially be
| made so), where the haskell and julia implementations only work
| on rank-1 ([a]) or rank-2 ([[a]]) arrays.
| dwohnitmok wrote:
| The Haskell one is polymorphic with respect to rank (and is
| indeed polymorphic even with respect to whether it's backed by
| a data structure or not).
|
| Haskell's infelicities for numeric code come from other places.
| moonchild wrote:
| For those haskell functions which operate on lists of
| arbitrary items, the items can indeed be list. However, the
| code is still fundamentally item-oriented. Those haskell
| functions which operate on lists of specific types (such as
| 'lowers', which operates on lists of characters) cannot
| readily be applied to higher-ranked lists of the same.
| occamrazor wrote:
| Julia's syntax is the same as Python's. It's very natural for
| short expressions, a bit cumbersome for multiline comprehension,
| because there is no obvious way to format the code.
|
| Another gripe I have is the order of the for clauses:
| [x for xs in xss for x in xs]
|
| should, in my opinion, be instead written as [x
| for x in xs for xs in xss]
|
| Also Haskell gets the order of '<-' clauses wrong, so probably
| mine is a minority opinion.
| berlinquin wrote:
| I can make sense of nested Python list comprehensions if I read
| them as shorthand for nested for loops, in which case you need
| to define variables in the outer loop before you can define
| dependent variables in the inner loop.
|
| e.g. with your example above, for xs in xss:
| for x in xs: x
|
| What's confusing about the syntax is that `x` in your example
| above comes first, whereas in actual nested loops it comes
| last. So maybe: [for xs in xss for x in xs : x]
|
| Could be alternative syntax that moves `x` to the end?
| BadInformatics wrote:
| Note that because blocks in Julia are expressions, you can do
| multi-line expressions in comprehensions that you wouldn't be
| able to do in Python: [ let y = x
| + 1 z = 2y -z end
| for x in 1:5 ]
| eesmith wrote:
| This specific case could be approximated with something like:
| >>> [(y:=x+1, z:= 2*y, -z)[-1] for x in range(1, 6)]
| [-4, -6, -8, -10, -12]
| romwell wrote:
| I'm with you on that minority opinion.
|
| The "for" side mimics nested for loops structure, but then the
| output statement goes first (?!).
|
| It's the same feel as MM/DD/YYYY date format: very widespread,
| yet absolutely asinine to anyone seeing it for the first time
| :)
| jolmg wrote:
| > Also Haskell gets the order of '<-' clauses wrong, so
| probably mine is a minority opinion.
|
| That's probably so it's more in-line with do-notation:
| [x | xs <- xss, x <- xs]
|
| is the same as: do { xs <- xss; x <- xs; return
| x }
|
| Maybe writing statements backwards was deemed more confusing.
|
| Personally, I don't really use list comprehension syntax, but
| maybe I would if they were backwards like you describe. It
| would make more sense with the position of the return value
| being to the left.
| dmitriid wrote:
| Haskell and Erlang basically go for mathematical notation.
| Python and Julia more or less go for how you read those
| notations.
|
| I personally prefer math-like notation because I find it much
| more readable and composable. x, such as x is
| in xs, xs is in xss x | x [?] xs, xs [?] xss # my
| math is rusty [x | x <- xs, xs <- xss] # haskell,
| erlang is isimilar [ x for xs in xss for x in xs ] #
| python, julia, order might be different
|
| As soon as you add conditions/filters on data, math-like
| notation wins hands down
|
| Edit:
|
| Since I don't know Haskell, apparently in Haskell the order is
| wrong. So, only Erlang gets it right (variables are upper case
| in Erlang): [ X || X <- Xs, Xs <- Xss]
| jolmg wrote:
| > only Erlang gets it right... [ X || X <- Xs, Xs <- Xss]
|
| I don't have much experience with Erlang, but that also seems
| wrong: 1> Xss = [[1]]. [[1]] 2> [
| X || X <- Xs, Xs <- Xss]. * 1:13: variable 'Xs' is
| unbound 3> [ X || Xs <- Xss, X <- Xs]. [1]
| dmitriid wrote:
| Damnit. I haven't used Erlang in a long time, but I
| should've known that.
|
| So, no language does it "right".
| montalbano wrote:
| Note that in Julia these are often referred to as array
| comprehensions, as the data structure involved is a (very
| efficient) array. C.f. Python
| kryptiskt wrote:
| And in Haskell they are monad comprehensions these days, so
| they can build more than lists. They actually used to be so
| back in the 90s, but Haskell 98 was conservative and restricted
| them to lists.
| exdsq wrote:
| What do monad comprehensions offer?
| thewakalix wrote:
| List comprehensions are a concise way of writing do-
| expressions in the list monad, so monad comprehensions just
| extend that to all monads.
| jolmg wrote:
| Being very generic. One can define a very efficient array
| structure, define its Monad instance and be able to use it
| in comprehensions. The point is that comprehensions are not
| fixed to a particular data structure by the language, and
| can be used with any user-definable Monad.
___________________________________________________________________
(page generated 2021-07-07 23:01 UTC)