Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2014-12

BOOL

27 messages · 12 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Adriaan van Os

While doing some Win32 programming I ran into a for loop that didn't work

	var b: bool;
	for b := False to True do ...

whereas

	var b: bool;
	for b := False downto True do ...

did work. The reason seems to be the internal value of False and True for BOOL, as

	program testbool;
	uses Windows;
	var b: BOOL;
	begin
	  b := False;
	  writeln
	    ( 'Ord( BOOL) for False = ', Ord( b));
	  b := True;
	  writeln
	    ( 'Ord( BOOL) for True  = ', Ord( b))
	end.

reveals 0 for False and -1 for True, where I had expected 0 for False and 1 for True. Also, according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the same test reveals 0 and 1 respectively in Visual Studio 2013.

So, there doesn't seem to be a WIndows compatibilty reason to use -1 for True (unless maybe if Delphi has -1 for True, but then it would apply to mode Delphi only).

Regards,

Adriaan van Os

Reply by email

Marco van de Voort
to Adriaan van Os

In our previous episode, Adriaan van Os said:

Show quoted text 6 lines
reveals 0 for False and -1 for True, where I had expected 0 for False and 1 f according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the same respectively in Visual Studio 2013.

There is a C (99?) bool type, and a winapi (and much older) BOOL type. Two different libraries, two different headers, two different cases :-)

In old SDKs, BOOL was defined in windef.h, but can't seem to quickly find that in my current 8.1 (that was seriously mangled due to the winrt additions).

Afaik BOOL was implemented for winapi benefit, and the boolean* types for pascal booleans (0,1)

More importantly, TRUE is generally defined as !FALSE, and vice versa (your URL mentiones that too), and thus not as succ(TRUE). So even if the values are successive, that doesn't mean it is ordinal other than being an integer derived value in older compilers.

So, there doesn't seem to be a WIndows compatibilty reason to use -1 for True Delphi has -1 for True, but then it would apply to mode Delphi only).

IMHO using the for loop on a non pascal boolean type is simply wrong. At the very least the assumption that the false and true are successive, and maybe even using BOOL as loopvar in the first place.

If I'm on my work machine (that still has a win7 SDK), I'll try to look it up there too.

Reply by email

waldo kitty
to Adriaan van Os: While doing some Win32 programming I ran into a for loop that didn't work var b: bool;…

my understanding of TRUE and FALSE has always been

FALSE = 0
TRUE = !FALSE

we have, in some of our code, used this to determine a "truth level" returned by some routines... in other words, when we're detected TRUE we've then looked at the value of the variable returning TRUE... depending on that value, we then branched based on the level of TRUE...

i don't know that we've done anything like this with any FPC code, though... the above was mostly in TP/BP stuffs and came in very very handy when it was implemented in our code...

NOTE: No off-list assistance is given without prior approval. Please *keep mailing list traffic on the list* unless private contact is specifically requested and granted.

Reply by email

Steve Hildebrandt
to waldo kitty
waldo kitty wrote 15 lines
my understanding of TRUE and FALSE has always been FALSE = 0 TRUE = !FALSE we have, in some of our code, used this to determine a "truth level" returned by some routines... in other words, when we're detected TRUE we've then looked at the value of the variable returning TRUE... depending on that value, we then branched based on the level of TRUE... i don't know that we've done anything like this with any FPC code, though... the above was mostly in TP/BP stuffs and came in very very handy when it was implemented in our code...

If you want to do such a thing in ObjectPascal I would use ByteBool, WordBool, ... insteadof the normal Boolean type.

mfg Steve Hildebrandt

Reply by email

Adriaan van Os
to Marco van de Voort: In our previous episode, Adriaan van Os said: There is a C (99?) bool type, and a winapi…
Marco van de Voort wrote 13 lines
In our previous episode, Adriaan van Os said: > reveals 0 for False and -1 for True, where I had expected 0 for False and > 1 f > according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the > same > respectively in Visual Studio 2013. There is a C (99?) bool type, and a winapi (and much older) BOOL type. Two different libraries, two different headers, two different cases :-) In old SDKs, BOOL was defined in windef.h, but can't seem to quickly find that in my current 8.1 (that was seriously mangled due to the winrt additions).

WIndef.h in the v7.0 WinSDK clearly defines

#ifdef TRUE
#undef TRUE
#endif
#define TRUE 1

Show quoted text 8 lines
Afaik BOOL was implemented for winapi benefit, and the boolean* types for pascal booleans (0,1) More importantly, TRUE is generally defined as !FALSE, and vice versa (your URL mentiones that too), and thus not as succ(TRUE). So even if the values are successive, that doesn't mean it is ordinal other than being an integer derived value in older compilers.

Well, not true = false and not false = true, that still holds. Which is not the same as not(ord(b))=ord(not(b)) which does not hold.

Show quoted text 8 lines
> So, there doesn't seem to be a WIndows compatibilty reason to use -1 for > True > Delphi has -1 for True, but then it would apply to mode Delphi only). IMHO using the for loop on a non pascal boolean type is simply wrong. At the very least the assumption that the false and true are successive, and maybe even using BOOL as loopvar in the first place.

The compiler accepts it.

Regards,

Adriaan van Os

Reply by email

Adriaan van Os
to Marco van de Voort: In our previous episode, Adriaan van Os said: There is a C (99?) bool type, and a winapi…
Marco van de Voort wrote 13 lines
In our previous episode, Adriaan van Os said: > reveals 0 for False and -1 for True, where I had expected 0 for False and > 1 f > according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the > same > respectively in Visual Studio 2013. There is a C (99?) bool type, and a winapi (and much older) BOOL type. Two different libraries, two different headers, two different cases :-) In old SDKs, BOOL was defined in windef.h, but can't seem to quickly find that in my current 8.1 (that was seriously mangled due to the winrt additions).

WIndef.h in the v7.0 WinSDK clearly defines

#ifdef TRUE
#undef TRUE
#endif
#define TRUE 1

Show quoted text 8 lines
Afaik BOOL was implemented for winapi benefit, and the boolean* types for pascal booleans (0,1) More importantly, TRUE is generally defined as !FALSE, and vice versa (your URL mentiones that too), and thus not as succ(TRUE). So even if the values are successive, that doesn't mean it is ordinal other than being an integer derived value in older compilers.

Well, not true = false and not false = true, that still holds. Which is not the same as not(ord(b))=ord(not(b)) which does not hold.

Show quoted text 8 lines
> So, there doesn't seem to be a WIndows compatibilty reason to use -1 for > True > Delphi has -1 for True, but then it would apply to mode Delphi only). IMHO using the for loop on a non pascal boolean type is simply wrong. At the very least the assumption that the false and true are successive, and maybe even using BOOL as loopvar in the first place.

The compiler accepts it.

Regards,

Adriaan van Os

Reply by email

Adriaan van Os
to Marco van de Voort: In our previous episode, Adriaan van Os said: There is a C (99?) bool type, and a winapi…
Marco van de Voort wrote 13 lines
In our previous episode, Adriaan van Os said: > reveals 0 for False and -1 for True, where I had expected 0 for False and > 1 f > according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the > same > respectively in Visual Studio 2013. There is a C (99?) bool type, and a winapi (and much older) BOOL type. Two different libraries, two different headers, two different cases :-) In old SDKs, BOOL was defined in windef.h, but can't seem to quickly find that in my current 8.1 (that was seriously mangled due to the winrt additions).

WIndef.h in the v7.0 WinSDK clearly defines

#ifdef TRUE
#undef TRUE
#endif
#define TRUE 1

Show quoted text 8 lines
Afaik BOOL was implemented for winapi benefit, and the boolean* types for pascal booleans (0,1) More importantly, TRUE is generally defined as !FALSE, and vice versa (your URL mentiones that too), and thus not as succ(TRUE). So even if the values are successive, that doesn't mean it is ordinal other than being an integer derived value in older compilers.

Well, not true = false and not false = true, that still holds. Which is not the same as not(ord(b))=ord(not(b)) which does not hold.

Show quoted text 8 lines
> So, there doesn't seem to be a WIndows compatibilty reason to use -1 for > True > Delphi has -1 for True, but then it would apply to mode Delphi only). IMHO using the for loop on a non pascal boolean type is simply wrong. At the very least the assumption that the false and true are successive, and maybe even using BOOL as loopvar in the first place.

The compiler accepts it.

Regards,

Adriaan van Os

Reply by email

Florian Klämpfl
to Adriaan van Os: WIndef.h in the v7.0 WinSDK clearly defines #ifdef TRUE #undef TRUE #endif #define TRUE 1
Adriaan van Os wrote 5 lines
> IMHO using the for loop on a non pascal boolean type is simply wrong. At the > very least the assumption that the false and true are successive, and maybe > even using BOOL as loopvar in the first place. The compiler accepts it.

Can somebody with access to delphi please check, if it compiles

var
  b : bytebool;
begin
  for b:=false to true do
    ;
end.

?

Reply by email

Michael Van Canneyt
to Florian Klämpfl
Florian Klämpfl wrote 16 lines
Am 14.12.2014 um 18:05 schrieb Adriaan van Os: >> IMHO using the for loop on a non pascal boolean type is simply wrong. At the >> very least the assumption that the false and true are successive, and maybe >> even using BOOL as loopvar in the first place. > > The compiler accepts it. Can somebody with access to delphi please check, if it compiles var b : bytebool; begin for b:=false to true do ; end.

D7 and Delphi XE5 compile it but do not generate code, instead say: 'FOR or WHILE loop executes zero times - deleted
variable b is declared but never used in ...

When declared as boolean, it does generate code.
(as I would expect)

Michael.

Reply by email

Florian Klämpfl
to Michael Van Canneyt
Michael Van Canneyt wrote 24 lines
On Sun, 14 Dec 2014, Florian Klämpfl wrote: > Am 14.12.2014 um 18:05 schrieb Adriaan van Os: >>> IMHO using the for loop on a non pascal boolean type is simply wrong. At the >>> very least the assumption that the false and true are successive, and maybe >>> even using BOOL as loopvar in the first place. >> >> The compiler accepts it. > > Can somebody with access to delphi please check, if it compiles > > var > b : bytebool; > > begin > for b:=false to true do > ; > end. D7 and Delphi XE5 compile it but do not generate code, instead say: 'FOR or WHILE loop executes zero times - deleted variable b is declared but never used in ...

But it generates code for

var
  b : bytebool;
begin
 for b:=true to false do
   ;
end.

?

Show quoted text 11 lines
When declared as boolean, it does generate code. (as I would expect) Michael. _______________________________________________ fpc-devel maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

Florian Klämpfl
to Michael Van Canneyt: Yes, it does. Strange behaviour :) Michael.
Michael Van Canneyt wrote 40 lines
On Sun, 14 Dec 2014, Florian Klämpfl wrote: > Am 14.12.2014 um 20:25 schrieb Michael Van Canneyt: >> >> >> On Sun, 14 Dec 2014, Florian Klämpfl wrote: >> >>> Am 14.12.2014 um 18:05 schrieb Adriaan van Os: >>>>> IMHO using the for loop on a non pascal boolean type is simply wrong. At the >>>>> very least the assumption that the false and true are successive, and maybe >>>>> even using BOOL as loopvar in the first place. >>>> >>>> The compiler accepts it. >>> >>> Can somebody with access to delphi please check, if it compiles >>> >>> var >>> b : bytebool; >>> >>> begin >>> for b:=false to true do >>> ; >>> end. >> >> D7 and Delphi XE5 compile it but do not generate code, instead say: >> 'FOR or WHILE loop executes zero times - deleted >> variable b is declared but never used in ... > > But it generates code for > > var > b : bytebool; > begin > for b:=true to false do > ; > end. Yes, it does. Strange behaviour :)

Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc mode ...

Reply by email

Michael Van Canneyt
to Florian Klämpfl: But it generates code for var b : bytebool; begin for b:=true to false do ; end. ?
Florian Klämpfl wrote 34 lines
Am 14.12.2014 um 20:25 schrieb Michael Van Canneyt: > > > On Sun, 14 Dec 2014, Florian Klämpfl wrote: > >> Am 14.12.2014 um 18:05 schrieb Adriaan van Os: >>>> IMHO using the for loop on a non pascal boolean type is simply wrong. At the >>>> very least the assumption that the false and true are successive, and maybe >>>> even using BOOL as loopvar in the first place. >>> >>> The compiler accepts it. >> >> Can somebody with access to delphi please check, if it compiles >> >> var >> b : bytebool; >> >> begin >> for b:=false to true do >> ; >> end. > > D7 and Delphi XE5 compile it but do not generate code, instead say: > 'FOR or WHILE loop executes zero times - deleted > variable b is declared but never used in ... But it generates code for var b : bytebool; begin for b:=true to false do ; end.

Yes, it does. Strange behaviour :)

Michael.

Reply by email

Michael Van Canneyt
to Florian Klämpfl: Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc mode ...
Florian Klämpfl wrote 43 lines
Am 14.12.2014 um 21:22 schrieb Michael Van Canneyt: > > > On Sun, 14 Dec 2014, Florian Klämpfl wrote: > >> Am 14.12.2014 um 20:25 schrieb Michael Van Canneyt: >>> >>> >>> On Sun, 14 Dec 2014, Florian Klämpfl wrote: >>> >>>> Am 14.12.2014 um 18:05 schrieb Adriaan van Os: >>>>>> IMHO using the for loop on a non pascal boolean type is simply wrong. At the >>>>>> very least the assumption that the false and true are successive, and maybe >>>>>> even using BOOL as loopvar in the first place. >>>>> >>>>> The compiler accepts it. >>>> >>>> Can somebody with access to delphi please check, if it compiles >>>> >>>> var >>>> b : bytebool; >>>> >>>> begin >>>> for b:=false to true do >>>> ; >>>> end. >>> >>> D7 and Delphi XE5 compile it but do not generate code, instead say: >>> 'FOR or WHILE loop executes zero times - deleted >>> variable b is declared but never used in ... >> >> But it generates code for >> >> var >> b : bytebool; >> begin >> for b:=true to false do >> ; >> end. > > Yes, it does. Strange behaviour :) Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc mode ...

We should. IMHO ByteBool is unsigned so >=0...

Michael.

Reply by email

Adriaan van Os
to Michael Van Canneyt
Michael Van Canneyt wrote
> Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc > mode ... We should. IMHO ByteBool is unsigned so >=0...
I suggest to change the internal value of TRUE to 1 for all boolean types, except in mode delphi.
Then "for b:= false to true" works as one would except.

Regards,

Adriaan van Os

Reply by email

Marco van de Voort
to Adriaan van Os

In our previous episode, Adriaan van Os said:

Show quoted text 8 lines
Michael Van Canneyt wrote: >> Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc >> mode ... > > We should. IMHO ByteBool is unsigned so >=0... I suggest to change the internal value of TRUE to 1 for all boolean types, except in mode delphi. Then "for b:= false to true" works as one would except.

For variant bools it is definitely -1. There is a comment about that too somewhere.

Reply by email

Tomas Hajny
to Adriaan van Os: I suggest to change the internal value of TRUE to 1 for all boolean types, except in mode…
Adriaan van Os wrote 9 lines
Michael Van Canneyt wrote: >> Well, true is -1 and false 0 :) I wonder if we should forbid it in fpc >> mode ... > > We should. IMHO ByteBool is unsigned so >=0... I suggest to change the internal value of TRUE to 1 for all boolean types, except in mode delphi. Then "for b:= false to true" works as one would except.

Ord (true) = 1 even now (in FPC as well as in other Pascal compilers). However, ByteBool type is a special type provided for compatibility with Delphi (which introduced it for easier interfacing with certain MS Windows API functions if I remember correctly) and that type requires a different convention, so the value is transformed during the implicit conversion. Changing the definition of type ByteBool for non-Delphi modes wouldn't make much sense in my view. From my point of view, the primary solution for you would be simply to stick to standard Pascal type boolean where it works as expected. A hint about possibly unexpected behaviour of ByteBool in for..do loops would be probably useful.

Tomas

Reply by email

Michael Schnell
to Marco van de Voort: In our previous episode, Adriaan van Os said: There is a C (99?) bool type, and a winapi…
Marco van de Voort wrote
More importantly, TRUE is generally defined as !FALSE, and vice versa

IMHO, (usually, ubiquitously ) FALSE is defined as a binary zero of appropriate bit count (hence unambiguously) , while TRUE is defined as "not FALSE" and hence it's binary representation is an implementation detail of the compiler and/or the processor the code is compiled for. (E.g. the 68 K processor can set a variable according to a condition code. In this instruction It uses all bits zero for FALSE and all bits one for TRUE. A compiler might or might not follow this paradigm.

A colleague of mine recently had been hit by this fact, as he called a C function in a DLL by a Delphi program.

In Delphi, TRUE is all bits set while in (Microsoft) C TRUE is "1".

Now, in C (which does not feature a native boolean type, but boolean operators) a *decent* boolean decision is e.g. "if (a)". But the silly C programmer did "if (a ==TRUE). Now the boolean variable "a" imported from Delphi was presumed as FALSE instead of TRUE.

-Michael

Reply by email

Adriaan van Os
to Michael Schnell: IMHO, (usually, ubiquitously ) FALSE is defined as a binary zero of appropriate bit…
Michael Schnell wrote
Now, in C (which does not feature a native boolean type, but boolean operators) a *decent* boolean decision is e.g. "if (a)". But the silly C programmer did "if (a ==TRUE). Now the boolean variable "a" imported from Delphi was presumed as FALSE instead of TRUE.

An interesting example ! It clearly shows the dangers of a language that isn't strictly typed and thus doesn't have implicit type conversions.

Regards,

Adriaan van Os

Reply by email

Mark Morgan Lloyd
to Adriaan van Os: WIndef.h in the v7.0 WinSDK clearly defines #ifdef TRUE #undef TRUE #endif #define TRUE 1
Adriaan van Os wrote 27 lines
Marco van de Voort wrote: > In our previous episode, Adriaan van Os said: >> reveals 0 for False and -1 for True, where I had expected 0 for False >> and >> 1 f >> according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the >> same >> respectively in Visual Studio 2013. > > There is a C (99?) bool type, and a winapi (and much older) BOOL type. > Two > different libraries, two different headers, two different cases :-) > > In old SDKs, BOOL was defined in windef.h, but can't seem to quickly find > that in my current 8.1 (that was seriously mangled due to the winrt > additions). WIndef.h in the v7.0 WinSDK clearly defines #ifdef TRUE #undef TRUE #endif #define TRUE 1 > > Afaik BOOL was implemented for winapi benefit, and the boolean* types for > pascal booleans (0,1)

With a slight health warning in that #undef etc. applies to the C preprocessor which is generally loosely-coupled to the underlying language. There might be cases where this model doesn't apply to Pascal implementations, which typically have integrated handling of compiler directives.

I agree that zero and false are generally equivalent, except possibly in the case of unix shell scripts where it gets messy. It's arguably unsafe to ever cast true to a number or enumeration, and possibly the best behaviour would be to ensure that the compiler always handled for b := false to not false do and for b := not false to false do the same.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]

Reply by email

Mark Morgan Lloyd
to Mark Morgan Lloyd
Mark Morgan Lloyd wrote 5 lines
I agree that zero and false are generally equivalent, except possibly in the case of unix shell scripts where it gets messy. It's arguably unsafe to ever cast true to a number or enumeration, and possibly the best behaviour would be to ensure that the compiler always handled for b := false to not false do and for b := not false to false do the same.

Should obviously have read b := not false to true do Need more caffeine. The salient point is that both give you full coverage of an enumerated type, so it's reasonable to expect every value to be iterated although the order might be undefined.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]

Reply by email

Hans-Peter Diettrich
to Marco van de Voort: In our previous episode, Adriaan van Os said: There is a C (99?) bool type, and a winapi…
Marco van de Voort wrote 8 lines
In our previous episode, Adriaan van Os said: > reveals 0 for False and -1 for True, where I had expected 0 for False and > 1 f > according to <http://msdn.microsoft.com/en-us/library/eke1xt9y.aspx> the > same > respectively in Visual Studio 2013. There is a C (99?) bool type, and a winapi (and much older) BOOL type. Two different libraries, two different headers, two different cases :-)

AFAIR Delphi defines some xxxBOOL types for the interpretation of *WinAPI function results*. If so, these types and values should be restricted to Windows platforms, not be used in general (cross-platform) code.

DoDi

Reply by email

Jasper Neumann

Hello folks!

Michael Schnell wrote 2014-12-15 09:28:05:

But the silly C programmer did "if (a == TRUE)".

I have seen this kind of nonsense way too often, even in Pascal code. May I sincerely ask to include a hint/warning feature such as "Comparing booleans - bad style"
in order to notify the programmer to think twice.
This warning should be also active when using booleans as case expression.

Best regards
Jasper

Reply by email

Marco van de Voort
to Jasper Neumann

In our previous episode, Jasper Neumann said:

Show quoted text 5 lines
I have seen this kind of nonsense way too often, even in Pascal code. May I sincerely ask to include a hint/warning feature such as "Comparing booleans - bad style" in order to notify the programmer to think twice. This warning should be also active when using booleans as case expression.

This thread is about C style booleans. For normal, safe behaviour, use Pascal booleans.

Reply by email

Jasper Neumann
to Jasper Neumann: Hello folks! Michael Schnell wrote 2014-12-15 09:28:05: I have seen this kind of nonsense…

Hello folks!

MS>>> But the silly C programmer did "if (a == TRUE)".

JN>> I have seen this kind of nonsense way too often,

Show quoted text 6 lines
> even in Pascal code. >May I sincerely ask to include a hint/warning feature such as > "Comparing booleans - bad style" >in order to notify the programmer to think twice. >This warning should be also active when using booleans > as case expression.
Marco van de Voor wrote
This thread is about C style booleans. For normal, safe behaviour, use Pascal booleans.

Well, yes, but...
I probably should better have changed the subject. My aim was to cure this kind of practice as it occurs on the Pascal side, although it might concern C compilers as well. I should propose this hint/warning for e.g. LLVM or GCC also.

Best regards
Jasper

Reply by email

DaWorm
to Jasper Neumann

Pascal is about readability. A sentence like "if this variable is true do that" reads better than "if this variable do that". The danger is only when the variable type and the constant representing TRUE are not compatible. In C, since TRUE is usually an untyped #define, that can be hard to determine by the compiler or parser. In Pascal, it should be easy to detect when the two arguments aren't defined as identical types, even if the types are assignable to each other. But as long as True and the affirmative type of Boolean match, there should be no need for a warning. Only if True is Boolean, and the variable is ByteBool, or WordBool, or BOOL or some other type that doesn't use the same True/False values as Boolean should a warning be needed.

In other words, it isn't a style issue, it is a type compatibility issue.

Jeff

Jasper Neumann wrote 29 lines
Hello folks! MS>>> But the silly C programmer did "if (a == TRUE)". JN>> I have seen this kind of nonsense way too often, >> even in Pascal code. >>May I sincerely ask to include a hint/warning feature such as >> "Comparing booleans - bad style" >>in order to notify the programmer to think twice. >>This warning should be also active when using booleans >> as case expression. Marco van de Voor wrote: > This thread is about C style booleans. > For normal, safe behaviour, use Pascal booleans. Well, yes, but... I probably should better have changed the subject. My aim was to cure this kind of practice as it occurs on the Pascal side, although it might concern C compilers as well. I should propose this hint/warning for e.g. LLVM or GCC also. Best regards Jasper _______________________________________________ fpc-devel maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email