a really juvenile question .. hehehehe ;^)

Hello, I am reading some extant Haskell code that uses Posix signals.... I am confused by the motivation of the following ... type Signalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...= CInthttp://hackage.haskell.org/packages/archive/base/3.0.1.0/doc/html/Foreign-C-... nullSignalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: Signalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos... internalAborthttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: Signalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos... sigABRThttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: CInthttp://hackage.haskell.org/packages/archive/base/3.0.1.0/doc/html/Foreign-C-... realTimeAlarmhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: Signalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos... sigALRMhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: CInthttp://hackage.haskell.org/packages/archive/base/3.0.1.0/doc/html/Foreign-C-... busErrorhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: Signalhttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos... sigBUShttp://hackage.haskell.org/packages/archive/unix/2.3.0.0/doc/html/System-Pos...:: CInthttp://hackage.haskell.org/packages/archive/base/3.0.1.0/doc/html/Foreign-C-... OK .. "type" is really just a synomym and doesn't invoke type checking like "data" type declarations do .. so why don't we have all the "CInts" substituted by "Signal"? I.e. what did I miss? Thanks, Vasili

vigalchin:
Hello,
I am reading some extant Haskell code that uses Posix signals.... I am confused by the motivation of the following ...
type [1]Signal = [2]CInt [3]nullSignal :: [4]Signal [5]internalAbort :: [6]Signal [7]sigABRT :: [8]CInt [9]realTimeAlarm :: [10]Signal [11]sigALRM :: [12]CInt [13]busError :: [14]Signal [15]sigBUS :: [16]CInt
OK .. "type" is really just a synomym and doesn't invoke type checking like "data" type declarations do .. so why don't we have all the "CInts" substituted by "Signal"? I.e. what did I miss?
Looks like it should all be Signal, and probably should be using a newtype, to prevent funky tricks. The Posix layer is a bit crufty. -- Don

Thanks Don. Maybe both for me and others in order to take the fight to the
Klingons and other Baddies, please explain the "typefulness" protection that
"newtype" affords over the "Klingon " "type" ... In the code that I
contributed to the library, I like to think that I used "newtype"
appropriately but not perhaps with full understanding.
Thanks, Vasili
On Mon, Oct 6, 2008 at 12:37 AM, Don Stewart
vigalchin:
Hello,
I am reading some extant Haskell code that uses Posix signals.... I am confused by the motivation of the following ...
type [1]Signal = [2]CInt [3]nullSignal :: [4]Signal [5]internalAbort :: [6]Signal [7]sigABRT :: [8]CInt [9]realTimeAlarm :: [10]Signal [11]sigALRM :: [12]CInt [13]busError :: [14]Signal [15]sigBUS :: [16]CInt
OK .. "type" is really just a synomym and doesn't invoke type checking like "data" type declarations do .. so why don't we have all the "CInts" substituted by "Signal"? I.e. what did I miss?
Looks like it should all be Signal, and probably should be using a newtype, to prevent funky tricks. The Posix layer is a bit crufty.
-- Don

Used wisely, newtype prevents accidentally constructing illegal values of Signal type, by treating them as CInts. You can restrict the valid values of the Signal type to be just those signals you define, not arbitrary bit patterns that fit in a CInt. vigalchin:
Thanks Don. Maybe both for me and others in order to take the fight to the Klingons and other Baddies, please explain the "typefulness" protection that "newtype" affords over the "Klingon " "type" ... In the code that I contributed to the library, I like to think that I used "newtype" appropriately but not perhaps with full understanding.
Thanks, Vasili
On Mon, Oct 6, 2008 at 12:37 AM, Don Stewart <[1]dons@galois.com> wrote:
vigalchin: > Hello, > > I am reading some extant Haskell code that uses Posix signals.... I am > confused by the motivation of the following ... > > type [1]Signal = [2]CInt > [3]nullSignal :: [4]Signal > [5]internalAbort :: [6]Signal > [7]sigABRT :: [8]CInt > [9]realTimeAlarm :: [10]Signal > [11]sigALRM :: [12]CInt > [13]busError :: [14]Signal > [15]sigBUS :: [16]CInt > > OK .. "type" is really just a synomym and doesn't invoke type checking > like "data" type declarations do .. so why don't we have all the "CInts" > substituted by "Signal"? I.e. what did I miss?
Looks like it should all be Signal, and probably should be using a newtype, to prevent funky tricks. The Posix layer is a bit crufty. -- Don
References
Visible links 1. mailto:dons@galois.com

ok ... by using "newtype", we are constricting/constraining to a subset of
CInt .. e.g. something like a "subtype" of CInt?? (where by "subtype", I
mean like the notion of subtype in languages like Ada). For our audience,
can you perhaps distinguish (in a typeful way) between the Haskell notion of
"type", "newtype" and "data"? Or maybe let's distinguish between these
notions not only in a typeful manner, but also in a historical motivation?
.. ... motivations are always IMO very, very enlightening!
Regards, vasili
On Mon, Oct 6, 2008 at 12:47 AM, Don Stewart
Used wisely, newtype prevents accidentally constructing illegal values of Signal type, by treating them as CInts. You can restrict the valid values of the Signal type to be just those signals you define, not arbitrary bit patterns that fit in a CInt.
Thanks Don. Maybe both for me and others in order to take the fight to
Klingons and other Baddies, please explain the "typefulness"
that "newtype" affords over the "Klingon " "type" ... In the code
contributed to the library, I like to think that I used "newtype" appropriately but not perhaps with full understanding.
Thanks, Vasili
On Mon, Oct 6, 2008 at 12:37 AM, Don Stewart <[1]dons@galois.com> wrote:
vigalchin: > Hello, > > I am reading some extant Haskell code that uses Posix signals.... I am > confused by the motivation of the following ... > > type [1]Signal = [2]CInt > [3]nullSignal :: [4]Signal > [5]internalAbort :: [6]Signal > [7]sigABRT :: [8]CInt > [9]realTimeAlarm :: [10]Signal > [11]sigALRM :: [12]CInt > [13]busError :: [14]Signal > [15]sigBUS :: [16]CInt > > OK .. "type" is really just a synomym and doesn't invoke type checking > like "data" type declarations do .. so why don't we have all
vigalchin: the protection that I the
"CInts" > substituted by "Signal"? I.e. what did I miss?
Looks like it should all be Signal, and probably should be using a newtype, to prevent funky tricks. The Posix layer is a bit crufty. -- Don
References
Visible links 1. mailto:dons@galois.com

2008/10/6 Galchin, Vasili
ok ... by using "newtype", we are constricting/constraining to a subset of CInt .. e.g. something like a "subtype" of CInt?? (where by "subtype", I mean like the notion of subtype in languages like Ada). For our audience, can you perhaps distinguish (in a typeful way) between the Haskell notion of "type", "newtype" and "data"? Or maybe let's distinguish between these notions not only in a typeful manner, but also in a historical motivation? .. ... motivations are always IMO very, very enlightening!
Here's an example of using newtypes: module Main where newtype Flag = Flag Int -- These are the only legal values: flag1 :: Flag flag1 = Flag 1 flag2 :: Flag flag2 = Flag 2 fun :: Int -> Flag -> Int fun n (Flag f) = undefined -- Implementation goes here. -- Using `fun`. main = do print (fun 10 flag1) -- Oh noes, the programmer messed up and reordered the -- arguments! print (fun flag1 10) Saved by the type checker: /home/tibell/Test.hs:21:13: Couldn't match expected type `Int' against inferred type `Flag' In the first argument of `fun', namely `flag1' In the first argument of `print', namely `(fun flag1 10)' In the expression: print (fun flag1 10) Failed, modules loaded: none. By creating a module that doesn't export the constructor used to create e.g. `Flag` and only the constants `flag1` and `flag2` we can make sure that no one ever calls `fun` with an illegal value. I hope this helps. Cheers, Johan

2008/10/5 Galchin, Vasili
ok ... by using "newtype", we are constricting/constraining to a subset of CInt .. e.g. something like a "subtype" of CInt?? (where by "subtype", I mean like the notion of subtype in languages like Ada). For our audience, can you perhaps distinguish (in a typeful way) between the Haskell notion of "type", "newtype" and "data"? Or maybe let's distinguish between these notions not only in a typeful manner, but also in a historical motivation? .. ... motivations are always IMO very, very enlightening!
If you like historical perspective check out this: http://research.microsoft.com/~simonpj/papers/history-of-haskell/index.htm type is similar to typedef in C. That is, it's more or less just for renaming. I say "more or less" because I'm not sure exactly how the "renaming" works in the presence of rank-2 and higher types. data is essentially for declaring new data structures. As a result of this it also defines a new type. newtype is for reusing existing types or data structures but with a new distinct type. data and newtype vary in one more subtle way, and that's how/when they evaluate to bottom. Most of the time they behave identically, but in the right cases they act sightly differently. newtype is usually regarded as more efficient than data. This is because the compiler can choose to optimize away the newtype so that it only exists at type check time. I think this is also possible with data in some, but not all, uses. Jason

dagit:
data and newtype vary in one more subtle way, and that's how/when they evaluate to bottom. Most of the time they behave identically, but in the right cases they act sightly differently. newtype is usually regarded as more efficient than data. This is because the compiler can choose to optimize away the newtype so that it only exists at type check time. I think this is also possible with data in some, but not all, uses.
The compiler *must* optimise away the use. They're sort of 'virtual' data, guaranteed to have no runtime cost. -- Don

2008/10/6 Don Stewart
dagit:
data and newtype vary in one more subtle way, and that's how/when they evaluate to bottom. Most of the time they behave identically, but in the right cases they act sightly differently. newtype is usually regarded as more efficient than data. This is because the compiler can choose to optimize away the newtype so that it only exists at type check time. I think this is also possible with data in some, but not all, uses.
The compiler *must* optimise away the use. They're sort of 'virtual' data, guaranteed to have no runtime cost.
I'm not sure that I'd want to be that emphatic about what an implementation *must* do regarding something so operational. The informal semantics of pattern matching in the Report says: Matching the pattern con pat against a value, where con is a constructor defined by newtype, depends on the value: * If the value is of the form con v, then pat is matched against v. * If the value is _|_, then pat is matched against _|_. That is, constructors associated with newtype serve only to change the type of a value. This clearly has an implementation which introduces no overhead, which one can expect from good implementations of the language. There are obviously implementations of these semantics which do introduce overhead as well though, so I would be hesitant to make any requirement like that. We can say however that newtypes have no additional runtime cost in GHC regardless of the optimisation level you pick. - Cale

On Mon, Oct 6, 2008 at 14:58, Cale Gibbard
2008/10/6 Don Stewart
: dagit:
data and newtype vary in one more subtle way, and that's how/when they evaluate to bottom. Most of the time they behave identically, but in the right cases they act sightly differently. newtype is usually regarded as more efficient than data. This is because the compiler can choose to optimize away the newtype so that it only exists at type check time. I think this is also possible with data in some, but not all, uses.
The compiler *must* optimise away the use. They're sort of 'virtual' data, guaranteed to have no runtime cost.
I'm not sure that I'd want to be that emphatic about what an implementation *must* do regarding something so operational.
The informal semantics of pattern matching in the Report says:
Matching the pattern con pat against a value, where con is a constructor defined by newtype, depends on the value: * If the value is of the form con v, then pat is matched against v. * If the value is _|_, then pat is matched against _|_. That is, constructors associated with newtype serve only to change the type of a value.
This clearly has an implementation which introduces no overhead, which one can expect from good implementations of the language. There are obviously implementations of these semantics which do introduce overhead as well though, so I would be hesitant to make any requirement like that.
And this requirement is there why? Is it specifically put in so that one is able to create this overhead-less implementation? Given: data A = A Int newtype B = B Int ta (A x) = True tb (B x) = True This happens (not surprisingly given your above comments): *Main GOA> :load test.hs [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main GOA> ta undefined *** Exception: Prelude.undefined *Main GOA> tb undefined True Why is the x evaluated in ta? cheers, Arnar

On Mon, Oct 6, 2008 at 2:19 PM, Arnar Birgisson
And this requirement is there why? Is it specifically put in so that one is able to create this overhead-less implementation?
Given:
data A = A Int newtype B = B Int
ta (A x) = True tb (B x) = True
This happens (not surprisingly given your above comments):
*Main GOA> :load test.hs [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main GOA> ta undefined *** Exception: Prelude.undefined *Main GOA> tb undefined True
Why is the x evaluated in ta?
x isn't evaluated. "undefined" is evaluated to see if it matches the constructor "A". But we don't even get to check, because undefined throws an exception during its evaluation. In the "tb" case, (B x) always matches because B is a newtype. x gets bound to undefined, but never evaluated. -- ryan

Hi,
On Mon, Oct 6, 2008 at 16:10, Ryan Ingram
On Mon, Oct 6, 2008 at 2:19 PM, Arnar Birgisson
wrote: And this requirement is there why? Is it specifically put in so that one is able to create this overhead-less implementation?
Given:
data A = A Int newtype B = B Int
ta (A x) = True tb (B x) = True
This happens (not surprisingly given your above comments):
*Main GOA> :load test.hs [1 of 1] Compiling Main ( test.hs, interpreted ) Ok, modules loaded: Main. *Main GOA> ta undefined *** Exception: Prelude.undefined *Main GOA> tb undefined True
Why is the x evaluated in ta?
x isn't evaluated.
Yes, realized my error just after hitting send :/
"undefined" is evaluated to see if it matches the constructor "A". But we don't even get to check, because undefined throws an exception during its evaluation.
In the "tb" case, (B x) always matches because B is a newtype. x gets bound to undefined, but never evaluated.
And this happens because data values are basically pattern matched at run-time but newtype values are matched at compile-time, effectively turning tb into an Int -> Bool function? That explains pretty well why newtype can have only one constructor. cheers, Arnar

On Mon, Oct 6, 2008 at 3:30 PM, Arnar Birgisson
"undefined" is evaluated to see if it matches the constructor "A". But we don't even get to check, because undefined throws an exception during its evaluation.
In the "tb" case, (B x) always matches because B is a newtype. x gets bound to undefined, but never evaluated.
And this happens because data values are basically pattern matched at run-time but newtype values are matched at compile-time, effectively turning tb into an Int -> Bool function?
Yep, that's exactly it.
That explains pretty well why newtype can have only one constructor.
I never thought of it that way, but yes, it really does! Also, you can get the same behavior out of "ta" if you write it like this: ta ~(A x) = True The translation looks something like this: f ~(A x) = e => f a = e where x = case a of (A v) -> v -- a,v fresh variables not mentioned in e (or, equivalently) f a = let (A x) = a in e -- "a" some fresh variable not mentioned in e This delays the pattern-matching (and thus, the evaluation of "a") lazily until "x" is demanded, at which point "a" might throw an exception or infinite loop, or, if the type has more than one constructor, fail to pattern match (which also throws an exception). If "x" is never demanded then neither is "a". -- ryan

On Mon, Oct 6, 2008 at 18:48, Ryan Ingram
Also, you can get the same behavior out of "ta" if you write it like this:
ta ~(A x) = True
The translation looks something like this:
f ~(A x) = e => f a = e where x = case a of (A v) -> v -- a,v fresh variables not mentioned in e
(or, equivalently)
f a = let (A x) = a in e -- "a" some fresh variable not mentioned in e
This delays the pattern-matching (and thus, the evaluation of "a") lazily until "x" is demanded, at which point "a" might throw an exception or infinite loop, or, if the type has more than one constructor, fail to pattern match (which also throws an exception). If "x" is never demanded then neither is "a".
Ah, that's pretty neat and subtle. Now, say I have a type created with data that has only one constructor. Couldn't the compiler optimize away this unneccessary evaluation during pattern matching? I.e. it would make what you just wrote implicit in that case. Or perhaps data declarations with just one ctor should really be turned into newtypes by the programmer? cheers, Arnar

2008/10/6 Arnar Birgisson
Ah, that's pretty neat and subtle. Now, say I have a type created with data that has only one constructor. Couldn't the compiler optimize away this unneccessary evaluation during pattern matching? I.e. it would make what you just wrote implicit in that case. Or perhaps data declarations with just one ctor should really be turned into newtypes by the programmer?
Well, the trouble is that because there are differences in the termination behaviour of programs depending on whether something is a newtype or a data with a single constructor, I think automatic conversion of one to the other is avoided.

On Mon, Oct 6, 2008 at 2:58 PM, Cale Gibbard
2008/10/6 Don Stewart
: dagit:
data and newtype vary in one more subtle way, and that's how/when they evaluate to bottom. Most of the time they behave identically, but in the right cases they act sightly differently. newtype is usually regarded as more efficient than data. This is because the compiler can choose to optimize away the newtype so that it only exists at type check time. I think this is also possible with data in some, but not all, uses.
The compiler *must* optimise away the use. They're sort of 'virtual' data, guaranteed to have no runtime cost.
I'm not sure that I'd want to be that emphatic about what an implementation *must* do regarding something so operational.
[..]
We can say however that newtypes have no additional runtime cost in GHC regardless of the optimisation level you pick.
Not even that is true in general. One can in general end up doing unnecessary work just for the sake of converting types. Suppose you have a newtype Price = Price Int and you're given [Int] and want to have [Price]. This is simple to do, just 'map Price'. But since Price and Int are represented the same way this ought to be just the identity function. But it is in general very difficult for a compiler to figure out that this traversal of the list in fact is just the identity function. Simple type conversions like these can unfortunately force you to do some work even though the representation is identical. Cheers, Josef
participants (8)
-
Arnar Birgisson
-
Cale Gibbard
-
Don Stewart
-
Galchin, Vasili
-
Jason Dagit
-
Johan Tibell
-
Josef Svenningsson
-
Ryan Ingram