POV-Ray : Newsgroups : povray.off-topic : Nobody will read this : Re: Nobody will read this Server Time
28 Jul 2024 18:22:04 EDT (-0400)
  Re: Nobody will read this  
From: Orchid Win7 v1
Date: 26 Jul 2013 17:58:37
Message: <51f2f10d@news.povray.org>
> [Note: When a type has only one value constructor function, it is
> customary for it to have the same name as the type. In other words,
>
> data Complex = Complex Double Double
>
> I have deliberately not done this, in the interests of clarity.]

It's perhaps not clear what I'm talking about here. Consider, for example

   data Complex = Grid Double Double | Polar Double Double

Now I can write a complex number in *two* ways:

   Grid 5 7

which represents 5 + 7i, or

   Polar 3 pi

which represents -3 (i.e., magnitude=3, argument=pi radians). I can then 
write

   mulCx (Grid r1 i1) (Grid r2 i2) = Grid (r1*r2 - i1*i2) (r1*i2 + r2*i1)
   mulCx (Polar m1 a1) (Polar m2 a2) = Polar (m1*m2) (a1+a2)

Unfortunately, if one number is Grid and the other is Polar, this 
function will throw a "pattern match failure" exception, because no 
pattern matches that exact combination. I'm not saying this approach is 
a good idea, I'm offering it as an example of what you *could* do.

Now, "Grid" and "Polar" are value constructors; they are ways of 
constructing (and de-constructing) data of the Complex type. But our 
original definition had only one constructor. Why have two names for one 
thing? This is why people typically write

   data Complex = Complex Double Double

I didn't, though, to make it clear when I'm talking about a type and 
when I'm talking about a constructor.

> In C-based languages, if-statements are a language primitive, whereas
> switch/case blocks are just syntax sugar for making nested if-blocks
> easier to write. In Haskell, the reverse is true; *pattern matching* is
> the fundamental language primitive behind all conditional branching. An
> if-expression is simply syntax sugar for a pattern match where the
> patterns are "True" and "False". Likewise, the "==" function is
> implemented in terms of pattern matching, NOT THE OTHER WAY AROUND! (You
> can pattern-match on stuff for which the "==" function isn't defined!)

The general way to do pattern-matching is with a case-block:

   case z of
     Grid  r i -> ...stuff...
     Polar m a -> ...stuff...

An if-expression is merely syntax sugar:

   if foo then bar else baz

   case foo of
     True  -> bar
     False -> baz

Writing multiple equations for a single function is also syntax sugar:

   isReal (Cx r 0) = True
   isReal _        = False

   isReal z =
     case z of
       Cx r 0 -> True
       _      -> False

> So much for putting stuff in and getting stuff out; how about some
> actual *arithmetic* now?
>
>  > addCx :: Complex -> Complex -> Complex
>  > addCx (Cx r1 i1) (Cx r2 i2) = Cx (r1 + r2) (i1 + i2)
>
> That type signature looks a bit weird, eh? The basic rule for reading
> function signatures is this: The *last* type is the return type of the
> function. All the *other* types are argument types. So if you see 5
> things separated by arrows, than you have a 4-argument function, and the
> 5th type is what the function returns.

If you want to make your head explode:

   addCx :: Complex -> (Complex -> Complex)

Similarly,

   quadratic :: Complex -> (Complex -> Complex)

This is why "quadratic c" is a valid expression; the result is a 
function of type Complex -> Complex.

> If you do compile this post, you may get several warnings about
> "undefined methods". The Num class actually defines more than just the
> three methods above. We've written an implementation for Num without
> implementing *all* of the prescribed methods. Haskell is usually very
> strict about correctness and compile-time checks. But not implementing
> all the methods of a class is a *run-time* error. (Or rather, trying to
> *use* one of the unimplemented methods is a run-time error. Since we
> aren't going to use any of the unimplemented methods, we'll be fine.)

Haskell's number classes are notoriously broken. They were defined very 
early on in Haskell's development. (Numbers are one of the very first 
constructs you're going to need out of a programming language!) 
Naturally, to change them now would massively break every shred of 
Haskell code ever written. On top of that, nobody can decide on a *less* 
broken alternative.

For the curious, the *actual* definition of Num is

   class Num n where
     (+) :: n -> n -> n
     (-) :: n -> n -> n
     (*) :: n -> n -> n
     negate :: n -> n
     abs    :: n -> n
     signum :: n -> n
     fromInteger :: n -> n

You may notice that abs is a rather stupid function for a complex number 
to have; what do you do? Take the absolute value of the real and 
imaginary parts separately? (Note that what you *can't* do is have abs 
return a real-value as the answer; it must be another complex value.) 
Ditto for signum, which usually returns -1, 0 or +1 for normal types. 
Finally, fromInteger converts an integer into a Complex (or whatever).

Note that I said earlier that "sum" now works for Complex. I was wrong; 
without fromInteger, you can't construct the complex number 0, which is 
what "sum" starts adding up from. Similarly, "product" wants to do 
"fromInteger 1" for its start value.

Things rapidly get vastly more broken if you want to handle *fractional* 
values. Notice (*) is here, but (/) is conspicuously missing? That's 
because integer types have "div", which does integer division, while 
fractional types have "/", which does normal division. (Integer types 
also have "mod", while fractional types do not.) Trigonometric functions 
are even more fun!

> Knowing this, let us define the following function:
>
>  > orbit :: Complex -> [Complex]
>  > orbit c = iterate (quadratic c) (Cx 0 0)

Now you see why it's Cx 0 0, rather than just 0. If you add

 > fromInteger r = Cx (fromInteger r) 0

to the instance declaration, then these problems go away. (And "sum" and 
"product" will start working properly.) Notice that "r" is an Integer, 
so we need to recursively call "fromInteger" to convert it to a Double.

> My, that's a lot of brackets! Fortunately, Haskell has something a bit
> like shell piping:
>
> count c = length $ take 15 $ takeWhile (\ z -> mag2 z <= 4) $ orbit c

This is not actually part of the language specification. Actually, it's 
just a user-defined operator with *really* low precedence:

 > ($) :: (x -> y) -> x -> y
 > f $ x = f x
 >
 > infixr 0 $

That *looks* like a really pointless function, but it's utility is in 
the operator precedence.

> We could also have done it this way:
>
> main = putStrLn (unlines output)

You can also do this:

 > main = writeFile "Mandelbrot.txt" (unlines output)

This then saves the data to a text file (in the system default encoding, 
with the system default end-of-line convention), rather than printing it 
to stdout.


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.