a really juvenile question .. hehehehe ;^)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

a really juvenile question .. hehehehe ;^)

Vasili I. Galchin
Hello,

   I am reading some extant Haskell code that uses Posix signals.... I am confused by the motivation of the following ...

type Signal = CInt

nullSignal :: Signal

internalAbort :: Signal

sigABRT :: CInt

realTimeAlarm :: Signal

sigALRM :: CInt

busError :: Signal

sigBUS :: 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?

Thanks, Vasili

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Don Stewart-2
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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Vasili I. Galchin
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 <[hidden email]> 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


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Don Stewart-2
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][hidden email]> 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:[hidden email]
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Vasili I. Galchin
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 <[hidden email]> wrote:
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][hidden email]> 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:[hidden email]


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Johan Tibell-2
2008/10/6 Galchin, Vasili <[hidden email]>:
> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Jason Dagit-2
In reply to this post by Vasili I. Galchin


2008/10/5 Galchin, Vasili <[hidden email]>
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


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Don Stewart-2
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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Cale Gibbard
2008/10/6 Don Stewart <[hidden email]>:

> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Arnar Birgisson
On Mon, Oct 6, 2008 at 14:58, Cale Gibbard <[hidden email]> wrote:

> 2008/10/6 Don Stewart <[hidden email]>:
>> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Josef Svenningsson
In reply to this post by Cale Gibbard
On Mon, Oct 6, 2008 at 2:58 PM, Cale Gibbard <[hidden email]> wrote:

> 2008/10/6 Don Stewart <[hidden email]>:
>> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Ryan Ingram
In reply to this post by Arnar Birgisson
On Mon, Oct 6, 2008 at 2:19 PM, Arnar Birgisson <[hidden email]> 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.  "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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Arnar Birgisson
Hi,

On Mon, Oct 6, 2008 at 16:10, Ryan Ingram <[hidden email]> wrote:

> On Mon, Oct 6, 2008 at 2:19 PM, Arnar Birgisson <[hidden email]> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Ryan Ingram
On Mon, Oct 6, 2008 at 3:30 PM, Arnar Birgisson <[hidden email]> wrote:

>> "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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Arnar Birgisson
On Mon, Oct 6, 2008 at 18:48, Ryan Ingram <[hidden email]> wrote:

> 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
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: a really juvenile question .. hehehehe ;^)

Cale Gibbard
2008/10/6 Arnar Birgisson <[hidden email]>:
> 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.
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe