Recently there has been a lot of discussion on this list about the programming language Clean and converting Clean programs to Haskell. Reading the Wikipedia article on the language, I can't really see any major difference between that and Haskell, except for the monads vs. uniqueness types.
So what's the deal with Clean? Why is it preferable to Haskell? Why is it not?
Deniz Dogan wrote: > Recently there has been a lot of discussion on this list about the > programming language Clean and converting Clean programs to Haskell. > Reading the Wikipedia article on the language, I can't really see any > major difference between that and Haskell, except for the monads vs. > uniqueness types.
> So what's the deal with Clean? Why is it preferable to Haskell? Why is it not?
As far as I can tell, Clean is to Haskell as C is to Pascal. I.e., Clean is notionally very similar to Haskell, but with lots of added clutter, complexity and general ugliness - but it's probably somehow more machine-efficient as a result.
(All of which makes the name "Clean" rather ironic, IMHO.)
Of course, this is merely the opinion I formed after performing a cursory scan of some of the introductory documentation. I haven't actually seen any code written with it or anything, so my opinion probably doesn't mean a lot...
>> Recently there has been a lot of discussion on this list about the >> programming language Clean and converting Clean programs to Haskell. >> Reading the Wikipedia article on the language, I can't really see any >> major difference between that and Haskell, except for the monads vs. >> uniqueness types.
>> So what's the deal with Clean? Why is it preferable to Haskell? Why is it >> not?
> As far as I can tell, Clean is to Haskell as C is to Pascal. I.e., Clean is > notionally very similar to Haskell, but with lots of added clutter, > complexity and general ugliness - but it's probably somehow more > machine-efficient as a result.
> (All of which makes the name "Clean" rather ironic, IMHO.)
> Of course, this is merely the opinion I formed after performing a cursory > scan of some of the introductory documentation. I haven't actually seen any > code written with it or anything, so my opinion probably doesn't mean a > lot...
It's preferable to Haskell in situations where Haskell isn't the best choice.
The criteria for that decision is different from problem to problem.
Example:
I had to implement a ring buffer, and I wanted the code using it to be written in Haskell. I ended up implementing the buffer in C, and wrapping it in FFI from Haskell because implementing a destructive array in Haskell is kind of unwieldy to someone of my experience level. In Clean, it looks like the uniqueness typing allows for destructive updates in a very controlled manner.
Disciplined Disciple might be interesting to look at here too, but i'm not sure I'd deploy anything with DDC just yet :-)
> I had to implement a ring buffer, and I wanted the code using it to be > written in Haskell. I ended up implementing the buffer in C, and wrapping > it in FFI from Haskell because implementing a destructive array in Haskell > is kind of unwieldy to someone of my experience level. In Clean, it looks > like the uniqueness typing allows for destructive updates in a very > controlled manner.
The ST monad provides this functionality. The never-instantiated-in-a-visible-way state parameter of the ST monad provides the "uniqueness" required for doing destructive updates in a pure way.
David Leimbach wrote: > Disciplined Disciple might be interesting to look at here too, but i'm not > sure I'd deploy anything with DDC just yet :-)
Indeed. What DDC needs most at the moment is more people working on it.
I've fixed a couple of bugs and I'm working on some others, but there are a large chunk of them in the bug tracker which are simply too deep for me with my current level of knowledge.
2009/11/3 Andrew Coppin <andrewcop...@btinternet.com>:
> As far as I can tell, Clean is to Haskell as C is to Pascal. I.e., Clean is > notionally very similar to Haskell, but with lots of added clutter, > complexity and general ugliness - but it's probably somehow more > machine-efficient as a result.
> (All of which makes the name "Clean" rather ironic, IMHO.)
Ouch - you really could put it the other way round.
Clean has very little clutter, though I suppose some people might take offence to it having simple macros (:==), but wait so does GHC via -XCPP...
I think Clean had generics before Haskell had Data.Generics, otherwise Haskell generally has more innovation, more people work on Haskell, Haskell's motivation was language research...
Clean has far fewer libraries, more people use Haskell...
> So what's the deal with Clean? Why is it preferable to Haskell? Why is it not?
Purely from glancing through the language reference, two things that it looks like clean has that I would love to have in haskell are better records and better arrays. The records don't implement any of the fancy subtyping stuff that the various haskell proposals do, but they have the benefits of a nicer syntax and actually being implemented.
Haskell arrays (by which I mean the IArray and MArray interfaces) are, to me at least, really hard to use. From little things like using closed ranges where the rest of the world uses half-open ones and opaque documentation and no consistency between IArray and MArray, to bigger things like how do you insert or delete something. My conclusion, after wrestling with ixmap for 15 minutes, was to convert to a list, concatMap across [(i, a)], convert back to an array, which has the bonus of runtime crashes if you forget an 'i'.
Sorry if this turned into a rant about arrays, but it's bothered me for a while :) I think the clean guys got it right when they decided to make good array support an explicit goal. I suppose haskell has since gone a different way with the various array fusion libraries with listlike interfaces, and maybe that's better in the long term. Maybe type families can make a nice interface someday. Meanwhile it's a mess though. _______________________________________________ Haskell-Cafe mailing list Haskell-C...@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
On Tue, Nov 3, 2009 at 2:16 PM, Tracy Wadleigh <tracy.wadle...@gmail.com>wrote:
> I had to implement a ring buffer, and I wanted the code using it to be >> written in Haskell. I ended up implementing the buffer in C, and wrapping >> it in FFI from Haskell because implementing a destructive array in Haskell >> is kind of unwieldy to someone of my experience level. In Clean, it looks >> like the uniqueness typing allows for destructive updates in a very >> controlled manner.
> The ST monad provides this functionality. The > never-instantiated-in-a-visible-way state parameter of the ST monad provides > the "uniqueness" required for doing destructive updates in a pure way.
Someone suggested that to me on IRC once I'd already cranked out a C implementation with FFI bindings. It's just too easy to use the FFI in Haskell :-)
If we raise the barrier of FFI, more people will use ST!
On Tue, Nov 3, 2009 at 3:14 PM, Ben Lippmeier <Ben.Lippme...@anu.edu.au>wrote:
> David Leimbach wrote:
>> Disciplined Disciple might be interesting to look at here too, but i'm not >> sure I'd deploy anything with DDC just yet :-)
> :) Nor would I (and I wrote most of it). I think the approach is right, but > the compiler itself is still in the "research prototype" stage.
> Ben.
I have to admit, the first time I hit the wiki page for DDC I said to myself "Self, this sounds crazy complicated". Then I read part of the PDF (your thesis I believe) about Region Types on the bus ride to work and thought. "Gee I think I scared myself off too quickly".
Uniqueness typing is quite interesting in Clean, but to control aliasing, like really *control* aliasing, that's just far out man.
So I still have to wrap my head around "why this isn't going to get completely out of control" and see why it's all safer than just writing C code but I must say the attention I will be paying to DDC has just gone quite a bit up.
> 2009/11/3 Andrew Coppin <andrewcop...@btinternet.com>:
> > As far as I can tell, Clean is to Haskell as C is to Pascal. I.e., Clean is > > notionally very similar to Haskell, but with lots of added clutter, > > complexity and general ugliness - but it's probably somehow more > > machine-efficient as a result.
> > (All of which makes the name "Clean" rather ironic, IMHO.)
Really, arrays in Haskell are the most @#!$! confusing thing in the world.
There's a bunch of different array structures.
I can't tell which one works best, and all I want to do is x[i] = value.
I thought uvector was the answer, you know, fast unboxed ARRAYs. Imagine my surprise when I saw this
indexU :: UA e => UArr e -> Int -> e
O(n). indexU extracts an element out of an immutable unboxed array.
An array implementation with an order N lookup. huh ?? That's not an array, that's a list. I was looking for an array.
However, I then found in the same hackage:
readMU :: MUArr e s -> Int -> ST s e
O(1). readMU reads the element at the specified index of a mutable unboxed array.
So O(1) for mutable, but O(n) for immutable ? See, confusing... I'm sure there's a really good, lofty type safety, something or other reason for that, that I'm sure I don't care about ;-)
There's also ST. So why is there a uvector, when there's ST ??
> On Tue, Nov 3, 2009 at 2:16 PM, Tracy Wadleigh <tracy.wadle...@gmail.com > > wrote:
> I had to implement a ring buffer, and I wanted the code using it to > be written in Haskell. I ended up implementing the buffer in C, and > wrapping it in FFI from Haskell because implementing a destructive > array in Haskell is kind of unwieldy to someone of my experience > level. In Clean, it looks like the uniqueness typing allows for > destructive updates in a very controlled manner.
> The ST monad provides this functionality. The never-instantiated-in- > a-visible-way state parameter of the ST monad provides the > "uniqueness" required for doing destructive updates in a pure way.
> Someone suggested that to me on IRC once I'd already cranked out a C > implementation with FFI bindings. It's just too easy to use the FFI > in Haskell :-)
> If we raise the barrier of FFI, more people will use ST!
In the presence of fusion (as is the case in uvector), it's hard to give meaningful time complexities for operations as they depend on what operations they are paired with. We need to think of a better way to express this behavior in the documentation though.
On Tue, Nov 3, 2009 at 9:12 PM, brian <bri...@aracnet.com> wrote: > Really, arrays in Haskell are the most @#!$! confusing thing in the world.
> There's a bunch of different array structures.
> I can't tell which one works best, and all I want to do is x[i] = value.
> I thought uvector was the answer, you know, fast unboxed ARRAYs. Imagine my > surprise when I saw this
> indexU :: UA e => UArr e -> Int -> e
> O(n). indexU extracts an element out of an immutable unboxed array.
> An array implementation with an order N lookup. huh ?? That's not an > array, that's a list. I was looking for an array.
> However, I then found in the same hackage:
> readMU :: MUArr e s -> Int -> ST s e
> O(1). readMU reads the element at the specified index of a mutable unboxed > array.
> So O(1) for mutable, but O(n) for immutable ? See, confusing... I'm sure > there's a really good, lofty type safety, something > or other reason for that, that I'm sure I don't care about ;-)
> There's also ST. So why is there a uvector, when there's ST ??
> etc, etc, etc...
> and then there's monads...
> other than that, having fun with haskell :-)
> Brian
> On Nov 3, 2009, at 3:42 PM, David Leimbach wrote:
>> On Tue, Nov 3, 2009 at 2:16 PM, Tracy Wadleigh <tracy.wadle...@gmail.com> >> wrote:
>> I had to implement a ring buffer, and I wanted the code using it to be >> written in Haskell. I ended up implementing the buffer in C, and wrapping >> it in FFI from Haskell because implementing a destructive array in Haskell >> is kind of unwieldy to someone of my experience level. In Clean, it looks >> like the uniqueness typing allows for destructive updates in a very >> controlled manner.
>> The ST monad provides this functionality. The >> never-instantiated-in-a-visible-way state parameter of the ST monad provides >> the "uniqueness" required for doing destructive updates in a pure way.
>> Someone suggested that to me on IRC once I'd already cranked out a C >> implementation with FFI bindings. It's just too easy to use the FFI in >> Haskell :-)
>> If we raise the barrier of FFI, more people will use ST!
Stephen Tetley wrote: > 2009/11/3 Andrew Coppin <andrewcop...@btinternet.com>:
>> As far as I can tell, Clean is to Haskell as C is to Pascal. I.e., Clean is >> notionally very similar to Haskell, but with lots of added clutter, >> complexity and general ugliness - but it's probably somehow more >> machine-efficient as a result.
>> (All of which makes the name "Clean" rather ironic, IMHO.)
> Ouch - you really could put it the other way round.
Part of this really comes down to how one feels about the monads vs uniqueness types argument. It's a silly argument to have since the ideas are orthogonal and only really intersect at IO, but there's history there which lead to the current state of things.
Sometimes in Haskell I've thought about how uniqueness typing would make something faster, but in general all the plumbing associated with it in Clean makes me feel like I'm writing systems-level code (i.e. C, asm) instead of using a high-level language. The extra plumbing really makes it feel dirtier to work with. That doesn't mean Clean is bad, but I think it does contribute to the "cluttered" feeling Haskellers get.
But as I said, it's a silly argument and folks should use whichever gives them warm fuzzies. I also have a vague unnameable distaste whenever working with Python, and rather enjoy working with Perl. Nobody's perfect :)
> In the presence of fusion (as is the case in uvector), it's hard to > give meaningful time complexities for operations as they depend on > what operations they are paired with. We need to think of a better way > to express this behavior in the documentation though.
I have to disagree here. Fusion never makes the complexity of operations worse. If it does, it's a bug.
Roman Leshchinskiy wrote: > On 04/11/2009, at 13:23, Daniel Peebles wrote:
>> In the presence of fusion (as is the case in uvector), it's hard to >> give meaningful time complexities for operations as they depend on >> what operations they are paired with. We need to think of a better way >> to express this behavior in the documentation though.
> I have to disagree here. Fusion never makes the complexity of operations > worse. If it does, it's a bug.
I think the point was more that the relevant complexity bound can change in the presence of fusion. For a poor example: the first map over a list is O(n) but all subsequent ones in a chain of maps are O(1) with fusion. I'm sure there are better examples than that, but you get the idea. Some people may care to know about that latter complexity rather than just the "independent" complexity.
While this comes up with fusion, it's not a new problem. The same sort of thing is gotten at by distinguishing worst-case vs average-case complexity, or amortized worst-case vs non-amortized wost-case, etc.
Actually, it's not a typo. If you look at the source, what you'll see is
indexU arr n = indexS (streamU arr) n
and then tracking down indexS, you'll see
indexS (Stream next s0 _) n0 | n0 < 0 = error "Data.Array.Vector.Stream.indexS: negative index" | otherwise = loop_index n0 s0 where loop_index n s = case next s of Yield x s' | n == 0 -> x | otherwise -> s' `seq` loop_index (n-1) s' Skip s' -> s' `seq` loop_index n s' Done -> error "Data.Array.Vector.Stream.indexS: index too large"
So in other words, indexU really does have O(n) complexity since it first converts the array into a stream and then walks down the stream in order to find the desired element.
Cheers, Greg
On Nov 3, 2009, at 6:25 PM, Roman Leshchinskiy wrote:
> Sometimes in Haskell I've thought about how uniqueness typing would > make something faster, but in general all the plumbing associated with > it in Clean makes me feel like I'm writing systems-level code (i.e. C, > asm) instead of using a high-level language. The extra plumbing really > makes it feel dirtier to work with. That doesn't mean Clean is bad, but > I think it does contribute to the "cluttered" feeling Haskellers get.
I think you're right here.
Haskell has developed something of an aversion to naming things that aren't important enough to have a name, such as variables whose only reason to exist is "plumbing". We'd far rather spend effort on more higher-order functions, monads, combinators and points-freeness than name something that's unimportant. And the funny thing about this is that it usually works, because in Haskell, abstraction is cheap.
I believe that this is the main reason why Haskell programmers haven't embraced arrows, despite their theoretical advantages: Every notation that has been implemented so far requires names for things that shouldn't need names.
> But as I said, it's a silly argument and folks should use whichever > gives them warm fuzzies.
I'd like to think that professional developers are a bit more scientific than this.
Brian wrote: > Really, arrays in Haskell are the most @#!$! confusing thing in the world.
Hi, Brian. I am having a great difficulty with arrays in Haskell. In the university where I study, functional programming is taught in Clean or in Haskell, depending on the professor who is teaching the subject in a given year. One year ago, when I took functional programming, the professor used Clean in his classes. I had no difficulty in learning how arrays and input/output work in Clean. In the case of arrays, the idea is very simple: One can update arrays, provided that s/he does not try to access the old array. Therefore, one needs to make a copy of any value of the old array that s/he will use before performing the update; the operation that makes copies also provides a new name for the array, that obliterates the old name. In order to get a better feeling of the thing, here is the `solvitī function, in Clean and Haskell (you can consider the # as a kind of do):
// Clean leftSide acc i j n arr | j >= n= (acc, arr); # (v, arr)= arr![j, n]; (a, arr)= arr![i, j]; = leftSide (acc-v*a) i (j+1) n arr;
solvit i n arr | i < 0 = arr # (a, arr)= arr![i, i]; (acc, arr)= arr![i, n]; (v, arr)= leftSide acc i (i+1) n arr; = solvit (i-1) n {arr&[i, n]= v/a};
-- HASKELL leftSide acc i j n arr | j>n= return acc leftSide acc i j n arr = do v <- readArray arr (j, n+1) a <- readArray arr (i, j) leftSide (acc-v*a) i (j+1) n arr
solvit i n arr | i<1= return () solvit i n arr= do a <- readArray arr (i, i) acc <- readArray arr (i, n+1) v <- leftSide acc i (i+1) n arr writeArray arr (i, n+1) $! (v/a) solvit (i-1) n arr
And here comes the reason for writing this article. In the previous version of the Gauss elimination algorithm, I have imported Data.Array.IO. I also wrote a version of the program that imports Data.Array.ST. The problem is that I don't know how to read an STUArray from a file, process it, and write it back to a file. Is it possible to transform it into an IOUArray pro tempore, read it, make it into an STUArray again in order to process it, and bring it back to IOUArray in order to print it? Below, you will find the Gauss elimination program in STUArray (by the way, it is slower than IOUArray). Could you modify the main function so it can read array `arrī from a file, and write the result to a file? Here is the Gauss Elimination for STUArray (the main function is the first one; modify it to read the array from a file, and write it back to a file):
main = do xs <- rnList (1.0,1000.0) args <- getArgs let (n, m)= dims args xx <- stToIO $ do arr <- newArray_ ((1,1),(n,m+1)) :: ST s (STUArray s (Int, Int) Double) fillArray xs 0.0 (1,n) (1,m) arr sLU arr n solvit n n arr x1 <- readArray arr (1, n+1) x2 <- readArray arr (1, n+1) return [x1, x2] print xx
{- -- Other option: main = do xs <- rnList (1.0,1000.0) args <- getArgs let (n, m)= dims args print $ runST $ do arr <- newArray_ ((1,1),(n,m+1)) :: ST s (STUArray s (Int, Int) Double) fillArray xs 0.0 (1,n) (1,m) arr sLU arr n solvit n n arr x1 <- readArray arr (1, n+1) x2 <- readArray arr (1, n+1) return [x1, x2] -}
fillArray xs s (i, n) (j, m) arr | i > n= return () fillArray xs s (i,n) (j, m) arr | i==n && j>m= do writeArray arr (i, j) $! s return () fillArray xs s (i, n) (j, m) arr | j > m = do writeArray arr (i, j) $! s fillArray xs 0.0 (i+1, n) (1, m) arr fillArray (val:xs) s (i, n) (j, m) arr= do writeArray arr (i, j) $! val fillArray xs (s+val) (i, n) (j+1, m) arr
sLU arr n= sIJ 2 1 2 n arr
sIJ i j k n arr | i > n = return () sIJ i j k n arr | k > n = sIJ (i+1) i (i+1) n arr sIJ i j k n arr = do {- im <- pmax (j+1) j swap j im 1 -} a <- readArray arr (k, j) forM_ [j..n+1] $ \l -> do ajj <- readArray arr (j, j) ajl <- readArray arr (j, l) akl <- readArray arr (k, l) writeArray arr (k, l) $! (akl-a*(ajl/ajj)) sIJ i j (k+1) n arr where pmax line imax | line > n = return imax pmax line imax = do alj <- readArray arr (line, j) aij <- readArray arr (imax, j) if (abs alj)> (abs aij) then pmax (line+1) line else pmax (line+1) imax swap r s q | q>n+1 = return () swap r s q | r==s = return () swap r s q = do arq <- readArray arr (r,q) asq <- readArray arr (s,q) writeArray arr (s,q) $! arq writeArray arr (r,q) $! asq swap r s (q+1)
leftSide acc i j n arr | j>n= return acc leftSide acc i j n arr = do v <- readArray arr (j, n+1) a <- readArray arr (i, j) leftSide (acc-v*a) i (j+1) n arr
solvit i n arr | i<1= return () solvit i n arr= do a <- readArray arr (i, i) acc <- readArray arr (i, n+1) v <- leftSide acc i (i+1) n arr writeArray arr (i, n+1) $! (v/a) solvit (i-1) n arr
--- On Tue, 11/3/09, brian <bri...@aracnet.com> wrote:
From: brian <bri...@aracnet.com> Subject: Re: [Haskell-cafe] What's the deal with Clean? To: "David Leimbach" <leim...@gmail.com> Cc: haskell-c...@haskell.org Received: Tuesday, November 3, 2009, 7:12 PM
Really, arrays in Haskell are the most @#!$! confusing thing in the world.
There's a bunch of different array structures.
I can't tell which one works best, and all I want to do is x[i] = value.
I thought uvector was the answer, you know, fast unboxed ARRAYs. Imagine my surprise when I saw this
indexU :: UA e => UArr e -> Int -> e
O(n). indexU extracts an element out of an immutable unboxed array.
An array implementation with an order N lookup. huh ?? That's not an array, that's a list. I was looking for an array.
However, I then found in the same hackage:
readMU :: MUArr e s -> Int -> ST s e
O(1). readMU reads the element at the specified index of a mutable unboxed array.
So O(1) for mutable, but O(n) for immutable ? See, confusing... I'm sure there's a really good, lofty type safety, something or other reason for that, that I'm sure I don't care about ;-)
There's also ST. So why is there a uvector, when there's ST ??
> On Tue, Nov 3, 2009 at 2:16 PM, Tracy Wadleigh <tracy.wadle...@gmail.com> wrote:
> I had to implement a ring buffer, and I wanted the code using it to be written in Haskell. I ended up implementing the buffer in C, and wrapping it in FFI from Haskell because implementing a destructive array in Haskell is kind of unwieldy to someone of my experience level. In Clean, it looks like the uniqueness typing allows for destructive updates in a very controlled manner.
> The ST monad provides this functionality. The never-instantiated-in-a-visible-way state parameter of the ST monad provides the "uniqueness" required for doing destructive updates in a pure way.
> Someone suggested that to me on IRC once I'd already cranked out a C implementation with FFI bindings. It's just too easy to use the FFI in Haskell :-)
> If we raise the barrier of FFI, more people will use ST!
__________________________________________________________________ Get the name you've always wanted @ymail.com or @rocketmail.com! Go to http://ca.promos.yahoo.com/jacko/
> Roman Leshchinskiy wrote: >> On 04/11/2009, at 13:23, Daniel Peebles wrote: >>> In the presence of fusion (as is the case in uvector), it's hard to >>> give meaningful time complexities for operations as they depend on >>> what operations they are paired with. We need to think of a better >>> way >>> to express this behavior in the documentation though. >> I have to disagree here. Fusion never makes the complexity of >> operations worse. If it does, it's a bug.
> I think the point was more that the relevant complexity bound can > change in the presence of fusion. For a poor example: the first map > over a list is O(n) but all subsequent ones in a chain of maps are > O(1) with fusion. I'm sure there are better examples than that, but > you get the idea. Some people may care to know about that latter > complexity rather than just the "independent" complexity.
I think asymptotic complexity is the wrong tool for what you're trying to do. You implement your algorithm using operations with known complexities. This allows you to compute the complexity of the entire algorithm. That's all you can use operation complexities for. The compiler is then free to optimise the algorithm as it sees fit but is supposed to preserve (or improve) its complexity. It is not guaranteed or even supposed to preserve the original operations. To stay with your example, each of the two maps is linear regardless of whether fusion happens. Executing the two maps, be it one after another or interlocked, is linear simply because O(n) + O(n) = O(n), not because of fusion.
Essentially, you're trying to use complexity to describe an optimisation which doesn't actually affect the complexity.
On 04/11/2009, at 14:07, Gregory Crosswhite wrote:
> Actually, it's not a typo. If you look at the source, what you'll > see is
> indexU arr n = indexS (streamU arr) n
I suspect it gets rewritten back to the O(1) version somewhere after is has had a chance to fuse. If not, then it's a bug. In the vector package, I do this instead, though:
indexU arr n = <O(1) implemetation>
{-# RULES
"indexU/unstreamU" forall s n. indexU (unstreamU s) n = indexS s n
On Nov 3, 2009, at 7:38 PM, Philippos Apolinarius wrote:
> Brian wrote: > > Really, arrays in Haskell are the most @#!$! confusing thing in > the world.
> Hi, Brian. > I am having a great difficulty with arrays in Haskell. In the > university where I study, functional programming is taught in Clean > or in
me too :-)
> And here comes the reason for writing this article. In the previous > version of the Gauss elimination algorithm, I have imported
you're asking me ?? I have no idea. I can't even figure out which package to use.
However if I had to guess, it seems to me that you want to read the data into a list and then find some ST function which can initialize an array using a list (maybe ?)