Prefer and and or for boolean operators

This commit is contained in:
Sam Mohr 2025-01-17 16:11:20 -08:00
parent d9d3fc74fc
commit a292e070d4
No known key found for this signature in database
GPG key ID: EA41D161A3C1BC99
35 changed files with 189 additions and 282 deletions

View file

@ -148,19 +148,19 @@ is_valid_char = \c ->
is_alpha_num : U8 -> Bool is_alpha_num : U8 -> Bool
is_alpha_num = \key -> is_alpha_num = \key ->
(key >= 48 && key <= 57) || (key >= 64 && key <= 90) || (key >= 97 && key <= 122) (key >= 48 and key <= 57) or (key >= 64 and key <= 90) or (key >= 97 and key <= 122)
# Convert a base64 character/digit to its index # Convert a base64 character/digit to its index
# See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table) # See also [Wikipedia](https://en.wikipedia.org/wiki/Base64#Base64_table)
unsafe_convert_char : U8 -> U8 unsafe_convert_char : U8 -> U8
unsafe_convert_char = \key -> unsafe_convert_char = \key ->
if key >= 65 && key <= 90 then if key >= 65 and key <= 90 then
# A-Z # A-Z
key - 65 key - 65
else if key >= 97 && key <= 122 then else if key >= 97 and key <= 122 then
# a-z # a-z
(key - 97) + 26 (key - 97) + 26
else if key >= 48 && key <= 57 then else if key >= 48 and key <= 57 then
# 0-9 # 0-9
(key - 48) + 26 + 26 (key - 48) + 26 + 26
else else

View file

@ -50,7 +50,7 @@ safe = \queen, diagonal, xs ->
when xs is when xs is
Nil -> Bool.true Nil -> Bool.true
Cons(q, t) -> Cons(q, t) ->
if queen != q && queen != q + diagonal && queen != q - diagonal then if queen != q and queen != q + diagonal and queen != q - diagonal then
safe(queen, (diagonal + 1), t) safe(queen, (diagonal + 1), t)
else else
Bool.false Bool.false

View file

@ -19,10 +19,8 @@ to_str = \@Variable(char) ->
from_utf8 : U8 -> Result Variable [InvalidVariableUtf8] from_utf8 : U8 -> Result Variable [InvalidVariableUtf8]
from_utf8 = \char -> from_utf8 = \char ->
if if
char char >= 0x61 # "a"
>= 0x61 # "a" and char <= 0x7A # "z"
&& char
<= 0x7A # "z"
then then
Ok(@Variable(char)) Ok(@Variable(char))
else else

View file

@ -457,21 +457,15 @@ pop_variable = \ctx ->
is_digit : U8 -> Bool is_digit : U8 -> Bool
is_digit = \char -> is_digit = \char ->
char char >= 0x30 # `0`
>= 0x30 # `0` and char <= 0x39 # `0`
&& char
<= 0x39 # `0`
is_whitespace : U8 -> Bool is_whitespace : U8 -> Bool
is_whitespace = \char -> is_whitespace = \char ->
char char == 0xA # new line
== 0xA # new line or char == 0xD # carriage return
|| char or char == 0x20 # space
== 0xD # carriage return or char == 0x9 # tab
|| char
== 0x20 # space
|| char
== 0x9 # tab
end_unexpected = \err -> end_unexpected = \err ->
when err is when err is

View file

@ -1,4 +1,4 @@
module [Bool, Eq, true, false, and, or, not, is_eq, is_not_eq] module [Bool, Eq, true, false, not, is_eq, is_not_eq]
## Defines a type that can be compared for total equality. ## Defines a type that can be compared for total equality.
## ##
@ -44,55 +44,6 @@ true = @Bool(True)
false : Bool false : Bool
false = @Bool(False) false = @Bool(False)
## Returns `Bool.true` when both inputs are `Bool.true`. This is equivalent to
## the logic [AND](https://en.wikipedia.org/wiki/Logical_conjunction)
## gate. The infix operator `&&` can also be used as shorthand for
## `Bool.and`.
##
## ```roc
## expect Bool.and(Bool.true, Bool.true) == Bool.true
## expect (Bool.true && Bool.true) == Bool.true
## expect (Bool.false && Bool.true) == Bool.false
## expect (Bool.true && Bool.false) == Bool.false
## expect (Bool.false && Bool.false) == Bool.false
## ```
##
## ## Performance Details
##
## In Roc the `&&` and `||` work the same way as any
## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like
## `enable_pets && likes_dogs(user)` would compile to.
## ```roc
## if enable_pets then
## likes_dogs(user)
## else
## Bool.false
## ```
## Roc does not do this because conditionals like `if` and `when` have a
## performance cost. Calling a function can sometimes be faster across the board
## than doing an `if` to decide whether to skip calling it.
and : Bool, Bool -> Bool
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`.
## ```roc
## expect Bool.or(Bool.false, Bool.true) == Bool.true
## expect (Bool.true || Bool.true) == Bool.true
## expect (Bool.false || Bool.true) == Bool.true
## expect (Bool.true || Bool.false) == Bool.true
## expect (Bool.false || Bool.false) == Bool.false
## ```
##
## ## Performance Details
##
## In Roc the `&&` and `||` work the same way as any
## other functions. However, in some languages `&&` and `||` are special-cased.
## Refer to the note in `Bool.and` for more detail.
or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is ## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation) ## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`. ## gate. The operator `!` can also be used as shorthand for `Bool.not`.

View file

@ -170,7 +170,7 @@ reserve = |@Dict({ buckets, data, max_bucket_capacity: original_max_bucket_capac
size = Num.min(requested_size, max_size) size = Num.min(requested_size, max_size)
requested_shifts = calc_shifts_for_size(size, max_load_factor) requested_shifts = calc_shifts_for_size(size, max_load_factor)
if List.is_empty(buckets) || requested_shifts > shifts then if List.is_empty(buckets) or requested_shifts > shifts then
(buckets0, max_bucket_capacity) = alloc_buckets_from_shift(requested_shifts, max_load_factor) (buckets0, max_bucket_capacity) = alloc_buckets_from_shift(requested_shifts, max_load_factor)
buckets1 = fill_buckets_from_data(buckets0, data, requested_shifts) buckets1 = fill_buckets_from_data(buckets0, data, requested_shifts)
@Dict( @Dict(
@ -915,7 +915,7 @@ calc_shifts_for_size_helper = |shifts, size, max_load_factor|
|> Num.to_f32 |> Num.to_f32
|> Num.mul(max_load_factor) |> Num.mul(max_load_factor)
|> Num.floor |> Num.floor
if shifts > 0 && max_bucket_capacity < size then if shifts > 0 and max_bucket_capacity < size then
calc_shifts_for_size_helper(Num.sub_wrap(shifts, 1), size, max_load_factor) calc_shifts_for_size_helper(Num.sub_wrap(shifts, 1), size, max_load_factor)
else else
shifts shifts
@ -1087,7 +1087,7 @@ expect
|> insert("bar", {}) |> insert("bar", {})
|> insert("baz", {}) |> insert("baz", {})
contains(dict, "baz") && !(contains(dict, "other")) contains(dict, "baz") and !(contains(dict, "other"))
expect expect
dict = dict =
@ -1145,17 +1145,17 @@ expect
|> insert("l", 11) |> insert("l", 11)
(get(dict, "a") == Ok(0)) (get(dict, "a") == Ok(0))
&& (get(dict, "b") == Ok(1)) and (get(dict, "b") == Ok(1))
&& (get(dict, "c") == Ok(2)) and (get(dict, "c") == Ok(2))
&& (get(dict, "d") == Ok(3)) and (get(dict, "d") == Ok(3))
&& (get(dict, "e") == Ok(4)) and (get(dict, "e") == Ok(4))
&& (get(dict, "f") == Ok(5)) and (get(dict, "f") == Ok(5))
&& (get(dict, "g") == Ok(6)) and (get(dict, "g") == Ok(6))
&& (get(dict, "h") == Ok(7)) and (get(dict, "h") == Ok(7))
&& (get(dict, "i") == Ok(8)) and (get(dict, "i") == Ok(8))
&& (get(dict, "j") == Ok(9)) and (get(dict, "j") == Ok(9))
&& (get(dict, "k") == Ok(10)) and (get(dict, "k") == Ok(10))
&& (get(dict, "l") == Ok(11)) and (get(dict, "l") == Ok(11))
# Force rehash. # Force rehash.
expect expect
@ -1197,18 +1197,18 @@ expect
|> insert("m", 12) |> insert("m", 12)
(get(dict, "a") == Ok(0)) (get(dict, "a") == Ok(0))
&& (get(dict, "b") == Ok(1)) and (get(dict, "b") == Ok(1))
&& (get(dict, "c") == Ok(2)) and (get(dict, "c") == Ok(2))
&& (get(dict, "d") == Ok(3)) and (get(dict, "d") == Ok(3))
&& (get(dict, "e") == Ok(4)) and (get(dict, "e") == Ok(4))
&& (get(dict, "f") == Ok(5)) and (get(dict, "f") == Ok(5))
&& (get(dict, "g") == Ok(6)) and (get(dict, "g") == Ok(6))
&& (get(dict, "h") == Ok(7)) and (get(dict, "h") == Ok(7))
&& (get(dict, "i") == Ok(8)) and (get(dict, "i") == Ok(8))
&& (get(dict, "j") == Ok(9)) and (get(dict, "j") == Ok(9))
&& (get(dict, "k") == Ok(10)) and (get(dict, "k") == Ok(10))
&& (get(dict, "l") == Ok(11)) and (get(dict, "l") == Ok(11))
&& (get(dict, "m") == Ok(12)) and (get(dict, "m") == Ok(12))
expect expect
empty({}) empty({})
@ -1266,7 +1266,7 @@ expect
bad_keys, bad_keys,
Bool.true, Bool.true,
|acc, k| |acc, k|
acc && Dict.contains(dict, k), acc and Dict.contains(dict, k),
) )
all_inserted_correctly all_inserted_correctly

View file

@ -1361,7 +1361,7 @@ split_last = |list, delimiter|
## result is an empty list. ## result is an empty list.
chunks_of : List a, U64 -> List (List a) chunks_of : List a, U64 -> List (List a)
chunks_of = |list, chunk_size| chunks_of = |list, chunk_size|
if chunk_size == 0 || List.is_empty(list) then if chunk_size == 0 or List.is_empty(list) then
[] []
else else
chunk_capacity = Num.div_ceil(List.len(list), chunk_size) chunk_capacity = Num.div_ceil(List.len(list), chunk_size)

View file

@ -619,9 +619,9 @@ is_gte : Num a, Num a -> Bool
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
is_approx_eq : Frac a, Frac a, { rtol ?? Frac a, atol ?? Frac a } -> Bool is_approx_eq : Frac a, Frac a, { rtol ?? Frac a, atol ?? Frac a } -> Bool
is_approx_eq = |x, y, { rtol ?? 0.00001, atol ?? 0.00000001 }| is_approx_eq = |x, y, { rtol ?? 0.00001, atol ?? 0.00000001 }|
eq = x <= y && x >= y eq = x <= y and x >= y
meets_tolerance = Num.abs_diff(x, y) <= Num.max(atol, (rtol * Num.max(Num.abs(x), Num.abs(y)))) meets_tolerance = Num.abs_diff(x, y) <= Num.max(atol, (rtol * Num.max(Num.abs(x), Num.abs(y))))
eq || meets_tolerance eq or meets_tolerance
## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise. ## Returns `Bool.true` if the number is `0`, and `Bool.false` otherwise.
is_zero : Num a -> Bool is_zero : Num a -> Bool

View file

@ -972,7 +972,7 @@ matches_at_help = |state|
}, },
) )
does_this_match && does_rest_match does_this_match and does_rest_match
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update ## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte. The index for that byte in the string is provided ## state for each byte. The index for that byte in the string is provided

View file

@ -212,8 +212,6 @@ map_symbol_to_lowlevel_and_arity! {
Eq; BOOL_STRUCTURAL_EQ; 2, Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2, NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
And; BOOL_AND; 2,
Or; BOOL_OR; 2,
Not; BOOL_NOT; 1, Not; BOOL_NOT; 1,
BoxExpr; BOX_BOX_FUNCTION; 1, BoxExpr; BOX_BOX_FUNCTION; 1,
UnboxExpr; BOX_UNBOX; 1, UnboxExpr; BOX_UNBOX; 1,

View file

@ -4,7 +4,6 @@ use crate::env::Env;
use crate::scope::Scope; use crate::scope::Scope;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::called_via::BinOp::{DoubleQuestion, Pizza, SingleQuestion};
use roc_module::called_via::{BinOp, CalledVia}; use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
@ -36,7 +35,7 @@ fn new_op_call_expr<'a>(
let value = match loc_op.value { let value = match loc_op.value {
// Rewrite the Pizza operator into an Apply // Rewrite the Pizza operator into an Apply
Pizza => { BinOp::Pizza => {
// Allow `left |> try (optional)` to desugar to `try left (optional)` // Allow `left |> try (optional)` to desugar to `try left (optional)`
let right_without_spaces = without_spaces(&right.value); let right_without_spaces = without_spaces(&right.value);
match right_without_spaces { match right_without_spaces {
@ -190,7 +189,7 @@ fn new_op_call_expr<'a>(
let args = args.into_bump_slice(); let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza)) Apply(function, args, CalledVia::BinOp(BinOp::Pizza))
} }
PncApply(function, arguments) => { PncApply(function, arguments) => {
let mut args = Vec::with_capacity_in(1 + arguments.len(), env.arena); let mut args = Vec::with_capacity_in(1 + arguments.len(), env.arena);
@ -200,16 +199,20 @@ fn new_op_call_expr<'a>(
let args = args.into_bump_slice(); let args = args.into_bump_slice();
Apply(function, args, CalledVia::BinOp(Pizza)) Apply(function, args, CalledVia::BinOp(BinOp::Pizza))
} }
Dbg => *desugar_dbg_expr(env, scope, left, region), Dbg => *desugar_dbg_expr(env, scope, left, region),
_ => { _ => {
// e.g. `1 |> (if b then (\a -> a) else (\c -> c))` // e.g. `1 |> (if b then (\a -> a) else (\c -> c))`
Apply(right, env.arena.alloc([left]), CalledVia::BinOp(Pizza)) Apply(
right,
env.arena.alloc([left]),
CalledVia::BinOp(BinOp::Pizza),
)
} }
} }
} }
DoubleQuestion => { BinOp::DoubleQuestion => {
let left = desugar_expr(env, scope, left); let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right); let right = desugar_expr(env, scope, right);
@ -259,7 +262,7 @@ fn new_op_call_expr<'a>(
When(left, &*env.arena.alloc([ok_branch, err_branch])) When(left, &*env.arena.alloc([ok_branch, err_branch]))
} }
SingleQuestion => { BinOp::SingleQuestion => {
let left = desugar_expr(env, scope, left); let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right); let right = desugar_expr(env, scope, right);
@ -340,6 +343,41 @@ fn new_op_call_expr<'a>(
When(left, &*env.arena.alloc([ok_branch, err_branch])) When(left, &*env.arena.alloc([ok_branch, err_branch]))
} }
BinOp::And => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
Expr::If {
if_thens: env.arena.alloc([(*left, *right)]),
final_else: env.arena.alloc(Loc::at(
right.region,
Expr::Var {
module_name: ModuleName::BOOL,
ident: "false",
},
)),
indented_else: false,
}
}
BinOp::Or => {
let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right);
Expr::If {
if_thens: env.arena.alloc([(
*left,
Loc::at(
right.region,
Expr::Var {
module_name: ModuleName::BOOL,
ident: "true",
},
),
)]),
final_else: right,
indented_else: false,
}
}
binop => { binop => {
let left = desugar_expr(env, scope, left); let left = desugar_expr(env, scope, left);
let right = desugar_expr(env, scope, right); let right = desugar_expr(env, scope, right);
@ -1641,8 +1679,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
GreaterThan => (ModuleName::NUM, "is_gt"), GreaterThan => (ModuleName::NUM, "is_gt"),
LessThanOrEq => (ModuleName::NUM, "is_lte"), LessThanOrEq => (ModuleName::NUM, "is_lte"),
GreaterThanOrEq => (ModuleName::NUM, "is_gte"), GreaterThanOrEq => (ModuleName::NUM, "is_gte"),
And => (ModuleName::BOOL, "and"), And => unreachable!("Cannot desugar the `and` operator"),
Or => (ModuleName::BOOL, "or"), Or => unreachable!("Cannot desugar the `or` operator"),
Pizza => unreachable!("Cannot desugar the |> operator"), Pizza => unreachable!("Cannot desugar the |> operator"),
DoubleQuestion => unreachable!("Cannot desugar the ?? operator"), DoubleQuestion => unreachable!("Cannot desugar the ?? operator"),
SingleQuestion => unreachable!("Cannot desugar the ? operator"), SingleQuestion => unreachable!("Cannot desugar the ? operator"),

View file

@ -959,8 +959,8 @@ fn push_op(buf: &mut Buf, op: BinOp) {
called_via::BinOp::GreaterThan => buf.push('>'), called_via::BinOp::GreaterThan => buf.push('>'),
called_via::BinOp::LessThanOrEq => buf.push_str("<="), called_via::BinOp::LessThanOrEq => buf.push_str("<="),
called_via::BinOp::GreaterThanOrEq => buf.push_str(">="), called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
called_via::BinOp::And => buf.push_str("&&"), called_via::BinOp::Or => buf.push_str("or"),
called_via::BinOp::Or => buf.push_str("||"), called_via::BinOp::And => buf.push_str("and"),
called_via::BinOp::Pizza => buf.push_str("|>"), called_via::BinOp::Pizza => buf.push_str("|>"),
called_via::BinOp::DoubleQuestion => buf.push_str("??"), called_via::BinOp::DoubleQuestion => buf.push_str("??"),
called_via::BinOp::SingleQuestion => buf.push_str("?"), called_via::BinOp::SingleQuestion => buf.push_str("?"),

View file

@ -1268,20 +1268,6 @@ trait Backend<'a> {
internal_error!("bitwise xor on a non-integer") internal_error!("bitwise xor on a non-integer")
} }
} }
LowLevel::And => {
if let LayoutRepr::Builtin(Builtin::Bool) = self.interner().get_repr(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise and on a non-integer")
}
}
LowLevel::Or => {
if let LayoutRepr::Builtin(Builtin::Bool) = self.interner().get_repr(*ret_layout) {
self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8)
} else {
internal_error!("bitwise or on a non-integer")
}
}
LowLevel::NumShiftLeftBy => { LowLevel::NumShiftLeftBy => {
if let LayoutRepr::Builtin(Builtin::Int(int_width)) = if let LayoutRepr::Builtin(Builtin::Int(int_width)) =
self.interner().get_repr(*ret_layout) self.interner().get_repr(*ret_layout)

View file

@ -1284,30 +1284,6 @@ pub(crate) fn run_low_level<'a, 'ctx>(
rhs_layout, rhs_layout,
) )
} }
And => {
// The (&&) operator
arguments!(lhs_arg, rhs_arg);
let bool_val = env.builder.new_build_and(
lhs_arg.into_int_value(),
rhs_arg.into_int_value(),
"bool_and",
);
BasicValueEnum::IntValue(bool_val)
}
Or => {
// The (||) operator
arguments!(lhs_arg, rhs_arg);
let bool_val = env.builder.new_build_or(
lhs_arg.into_int_value(),
rhs_arg.into_int_value(),
"bool_or",
);
BasicValueEnum::IntValue(bool_val)
}
Not => { Not => {
// The (!) operator // The (!) operator
arguments!(arg); arguments!(arg);

View file

@ -2163,14 +2163,6 @@ impl<'a> LowLevelCall<'a> {
NumF64ToParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_TO_PARTS), NumF64ToParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_TO_PARTS),
NumF32FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F32_FROM_PARTS), NumF32FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F32_FROM_PARTS),
NumF64FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_FROM_PARTS), NumF64FromParts => self.load_args_and_call_zig(backend, bitcode::NUM_F64_FROM_PARTS),
And => {
self.load_args(backend);
backend.code_builder.i32_and();
}
Or => {
self.load_args(backend);
backend.code_builder.i32_or();
}
Not => { Not => {
self.load_args(backend); self.load_args(backend);
backend.code_builder.i32_eqz(); backend.code_builder.i32_eqz();

View file

@ -6036,7 +6036,7 @@ All branches in an `if` must have the same type!
double_binop, double_binop,
indoc!( indoc!(
r" r"
key >= 97 && <= 122 key >= 97 and <= 122
" "
), ),
@r" @r"

View file

@ -62,8 +62,8 @@ const DISPLAY_STRINGS: [(BinOp, &str); 18] = [
(GreaterThan, ">"), (GreaterThan, ">"),
(LessThanOrEq, "<="), (LessThanOrEq, "<="),
(GreaterThanOrEq, ">="), (GreaterThanOrEq, ">="),
(And, "&&"), (And, "and"),
(Or, "||"), (Or, "or"),
]; ];
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -199,7 +199,7 @@ pub enum Associativity {
/// right-associative operators: /// right-associative operators:
/// ///
/// exponentiation: ^ /// exponentiation: ^
/// boolean: && || /// boolean: and or
/// application: <| /// application: <|
RightAssociative, RightAssociative,

View file

@ -109,8 +109,6 @@ pub enum LowLevel {
NumF64FromParts, NumF64FromParts,
Eq, Eq,
NotEq, NotEq,
And,
Or,
Not, Not,
Hash, Hash,
PtrCast, PtrCast,
@ -343,8 +341,6 @@ map_symbol_to_lowlevel! {
NumF64FromParts <= NUM_F64_FROM_PARTS; NumF64FromParts <= NUM_F64_FROM_PARTS;
Eq <= BOOL_STRUCTURAL_EQ; Eq <= BOOL_STRUCTURAL_EQ;
NotEq <= BOOL_STRUCTURAL_NOT_EQ; NotEq <= BOOL_STRUCTURAL_NOT_EQ;
And <= BOOL_AND;
Or <= BOOL_OR;
Not <= BOOL_NOT; Not <= BOOL_NOT;
Unreachable <= LIST_UNREACHABLE; Unreachable <= LIST_UNREACHABLE;
DictPseudoSeed <= DICT_PSEUDO_SEED; DictPseudoSeed <= DICT_PSEUDO_SEED;

View file

@ -1355,16 +1355,14 @@ define_builtins! {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
1 BOOL_FALSE: "false" 1 BOOL_FALSE: "false"
2 BOOL_TRUE: "true" 2 BOOL_TRUE: "true"
3 BOOL_AND: "and" 3 BOOL_NOT: "not"
4 BOOL_OR: "or" 4 BOOL_XOR: "xor"
5 BOOL_NOT: "not" 5 BOOL_NEQ: "is_not_eq"
6 BOOL_XOR: "xor" 6 BOOL_EQ: "Eq" exposed_type=true
7 BOOL_NEQ: "is_not_eq" 7 BOOL_IS_EQ: "is_eq"
8 BOOL_EQ: "Eq" exposed_type=true 8 BOOL_IS_EQ_IMPL: "bool_is_eq"
9 BOOL_IS_EQ: "is_eq" unexposed 9 BOOL_STRUCTURAL_EQ: "structural_eq"
10 BOOL_IS_EQ_IMPL: "bool_is_eq" unexposed 10 BOOL_STRUCTURAL_NOT_EQ: "structural_not_eq"
unexposed 11 BOOL_STRUCTURAL_EQ: "structural_eq"
unexposed 12 BOOL_STRUCTURAL_NOT_EQ: "structural_not_eq"
} }
5 STR: "Str" => { 5 STR: "Str" => {
0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias 0 STR_STR: "Str" exposed_apply_type=true // the Str.Str type alias

View file

@ -1562,7 +1562,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
Eq | NotEq => RC::NoRc, Eq | NotEq => RC::NoRc,
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated | NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf

View file

@ -1261,7 +1261,7 @@ pub(crate) fn lowlevel_borrow_signature(op: LowLevel) -> &'static [Ownership] {
Eq | NotEq => &[BORROWED, BORROWED], Eq | NotEq => &[BORROWED, BORROWED],
And | Or | NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap NumAdd | NumAddWrap | NumAddChecked | NumAddSaturated | NumSub | NumSubWrap
| NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated | NumSubChecked | NumSubSaturated | NumMul | NumMulWrap | NumMulSaturated
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivFrac
| NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumDivTruncUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf

View file

@ -738,7 +738,7 @@ fn parse_stmt_operator_chain<'a>(
| Expr::Apply( | Expr::Apply(
Loc { Loc {
region: _, region: _,
value: Expr::Tag(..) value: Expr::Tag(_)
}, },
&[], &[],
_ _
@ -752,7 +752,7 @@ fn parse_stmt_operator_chain<'a>(
// try an operator // try an operator
return parse_stmt_after_apply( return parse_stmt_after_apply(
arena, arena,
state.clone(), state,
min_indent, min_indent,
call_min_indent, call_min_indent,
expr_state, expr_state,
@ -1934,43 +1934,6 @@ fn parse_stmt_after_apply<'a>(
} }
} }
// #[allow(clippy::too_many_arguments)]
// fn parse_expr_after_apply<'a>(
// arena: &'a Bump,
// state: State<'a>,
// min_indent: u32,
// call_min_indent: u32,
// check_for_arrow: CheckForArrow,
// check_for_defs: bool,
// mut expr_state: ExprState<'a>,
// before_op: State<'a>,
// initial_state: State<'a>,
// ) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
// match loc(bin_op(check_for_defs)).parse(arena, state.clone(), call_min_indent) {
// Err((MadeProgress, f)) => Err((MadeProgress, f)),
// Ok((_, loc_op, state)) => {
// expr_state.consume_spaces(arena);
// let initial_state = before_op;
// parse_expr_operator(
// arena,
// state,
// min_indent,
// call_min_indent,
// options,
// check_for_defs,
// expr_state,
// loc_op,
// initial_state,
// )
// }
// Err((NoProgress, _)) => {
// let expr = parse_expr_final(expr_state, arena);
// // roll back space parsing
// Ok((MadeProgress, expr, initial_state))
// }
// }
// }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn parse_apply_arg<'a>( fn parse_apply_arg<'a>(
arena: &'a Bump, arena: &'a Bump,
@ -4069,6 +4032,24 @@ where
G: Fn(&'a str, Position) -> E, G: Fn(&'a str, Position) -> E,
E: 'a, E: 'a,
{ {
match state.bytes() {
&[b'o', b'r', ..] => {
return Ok((
MadeProgress,
OperatorOrDef::BinOp(BinOp::Or),
state.advance(2),
))
}
&[b'a', b'n', b'd', ..] => {
return Ok((
MadeProgress,
OperatorOrDef::BinOp(BinOp::And),
state.advance(3),
))
}
_ => {}
}
let chomped = chomp_ops(state.bytes()); let chomped = chomp_ops(state.bytes());
macro_rules! good { macro_rules! good {

View file

@ -10,6 +10,8 @@ pub const IMPORT: &str = "import";
pub const EXPECT: &str = "expect"; pub const EXPECT: &str = "expect";
pub const RETURN: &str = "return"; pub const RETURN: &str = "return";
pub const CRASH: &str = "crash"; pub const CRASH: &str = "crash";
pub const AND: &str = "and";
pub const OR: &str = "or";
// These keywords are valid in imports // These keywords are valid in imports
pub const EXPOSING: &str = "exposing"; pub const EXPOSING: &str = "exposing";
@ -21,8 +23,8 @@ pub const WHERE: &str = "where";
// These keywords are valid in headers // These keywords are valid in headers
pub const PLATFORM: &str = "platform"; pub const PLATFORM: &str = "platform";
pub const KEYWORDS: [&str; 11] = [ pub const KEYWORDS: [&str; 13] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, RETURN, CRASH, IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, RETURN, CRASH, AND, OR,
]; ];
pub fn is_allowed_identifier(mut ident: &str) -> bool { pub fn is_allowed_identifier(mut ident: &str) -> bool {

View file

@ -2095,13 +2095,13 @@ mod eq {
LyingEq := U8 implements [Eq {is_eq}] LyingEq := U8 implements [Eq {is_eq}]
is_eq = \@LyingEq m, @LyingEq n -> m != n is_eq = \@LyingEq(m), @LyingEq(n) -> m != n
main = main =
a = @LyingEq 10 a = @LyingEq(10)
b = @LyingEq 5 b = @LyingEq(5)
c = @LyingEq 5 c = @LyingEq(5)
if Bool.is_eq a b && !(Bool.is_eq b c) then if Bool.is_eq(a, b) and !(Bool.is_eq(b, c)) then
"okay" "okay"
else else
"fail" "fail"

View file

@ -121,7 +121,7 @@ fn bool_logic() {
bool2 = Bool.false bool2 = Bool.false
bool3 = !bool1 bool3 = !bool1
(bool1 && bool2) || bool2 && bool3 (bool1 and bool2) or bool2 and bool3
"# "#
), ),
false, false,
@ -132,19 +132,19 @@ fn bool_logic() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn and_bool() { fn and_bool() {
assert_evals_to!("Bool.true && Bool.true", true, bool); assert_evals_to!("Bool.true and Bool.true", true, bool);
assert_evals_to!("Bool.true && Bool.false", false, bool); assert_evals_to!("Bool.true and Bool.false", false, bool);
assert_evals_to!("Bool.false && Bool.true", false, bool); assert_evals_to!("Bool.false and Bool.true", false, bool);
assert_evals_to!("Bool.false && Bool.false", false, bool); assert_evals_to!("Bool.false and Bool.false", false, bool);
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn or_bool() { fn or_bool() {
assert_evals_to!("Bool.true || Bool.true", true, bool); assert_evals_to!("Bool.true or Bool.true", true, bool);
assert_evals_to!("Bool.true || Bool.false", true, bool); assert_evals_to!("Bool.true or Bool.false", true, bool);
assert_evals_to!("Bool.false || Bool.true", true, bool); assert_evals_to!("Bool.false or Bool.true", true, bool);
assert_evals_to!("Bool.false || Bool.false", false, bool); assert_evals_to!("Bool.false or Bool.false", false, bool);
} }
#[test] #[test]
@ -544,7 +544,7 @@ fn eq_different_rosetrees() {
cd = c2 == d2 cd = c2 == d2
ab && cd ab and cd
"# "#
), ),
true, true,

View file

@ -158,19 +158,19 @@ fn even_odd() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r" r"
even = \n -> even = |n|
when n is when n is
0 -> Bool.true 0 -> Bool.true
1 -> Bool.false 1 -> Bool.false
_ -> odd(n - 1) _ -> odd(n - 1)
odd = \n -> odd = |n|
when n is when n is
0 -> Bool.false 0 -> Bool.false
1 -> Bool.true 1 -> Bool.true
_ -> even(n - 1) _ -> even(n - 1)
odd 5 && even 42 odd(5) and even(42)
" "
), ),
true, true,
@ -2315,12 +2315,12 @@ fn recursive_tag_id_in_allocation_eq() {
] ]
x : Value x : Value
x = G 42 x = G(42)
y : Value y : Value
y = H 42 y = H(42)
main = (x == x) && (x != y) && (y == y) main = x == x and x != y and y == y
"# "#
), ),
true, true,

View file

@ -2460,7 +2460,7 @@ fn issue_4557() {
app "test" provides [main] to "./platform" app "test" provides [main] to "./platform"
is_eq_q = \q1, q2 -> when T q1 q2 is is_eq_q = \q1, q2 -> when T q1 q2 is
T (U f1) (U f2) -> Bool.or (is_eq_q (U f2) (U f1)) (f1 {} == f2 {}) T (U f1) (U f2) -> (is_eq_q (U f2) (U f1)) or (f1 {} == f2 {})
main = is_eq_q (U \{} -> "a") (U \{} -> "a") main = is_eq_q (U \{} -> "a") (U \{} -> "a")
"# "#

View file

@ -2,11 +2,11 @@ app "test" provides [main] to "./platform"
main = main =
s1 : Set U8 s1 : Set U8
s1 = Set.empty {} s1 = Set.empty({})
s2 : Set Str s2 : Set Str
s2 = Set.empty {} s2 = Set.empty({})
Bool.is_eq s1 s1 && Bool.is_eq s2 s2 Bool.is_eq(s1, s1) and Bool.is_eq(s2, s2)
# ^^^^^^^^^^ Set#Bool.is_eq(31): Set Str, Set Str -[[Set.is_eq(31)]]-> Bool # ^^^^^^^^^^ Set#Bool.is_eq(31): Set Str, Set Str -[[Set.is_eq(31)]]-> Bool
# ^^^^^^^^^^ Set#Bool.is_eq(31): Set U8, Set U8 -[[Set.is_eq(31)]]-> Bool # ^^^^^^^^^^ Set#Bool.is_eq(31): Set U8, Set U8 -[[Set.is_eq(31)]]-> Bool

View file

@ -1797,7 +1797,7 @@ generate_derive_str = \buf, types, type, include_debug ->
can_support_eq_hash_ord : Types, Shape -> Bool can_support_eq_hash_ord : Types, Shape -> Bool
can_support_eq_hash_ord = \types, type -> can_support_eq_hash_ord = \types, type ->
!(has_float(types, type)) && (can_support_partial_eq_ord(types, type)) !(has_float(types, type)) and (can_support_partial_eq_ord(types, type))
can_support_partial_eq_ord : Types, Shape -> Bool can_support_partial_eq_ord : Types, Shape -> Bool
can_support_partial_eq_ord = \types, type -> can_support_partial_eq_ord = \types, type ->
@ -1817,7 +1817,7 @@ can_support_partial_eq_ord = \types, type ->
k_type = Types.shape(types, k) k_type = Types.shape(types, k)
v_type = Types.shape(types, v) v_type = Types.shape(types, v)
can_support_partial_eq_ord(types, k_type) && can_support_partial_eq_ord(types, v_type) can_support_partial_eq_ord(types, k_type) and can_support_partial_eq_ord(types, v_type)
TagUnion(Recursive({ tags })) -> TagUnion(Recursive({ tags })) ->
List.all(tags, \{ payload } -> List.all(tags, \{ payload } ->
@ -1854,7 +1854,7 @@ can_support_partial_eq_ord = \types, type ->
ok_shape = Types.shape(types, ok_id) ok_shape = Types.shape(types, ok_id)
err_shape = Types.shape(types, err_id) err_shape = Types.shape(types, err_id)
can_support_partial_eq_ord(types, ok_shape) && can_support_partial_eq_ord(types, err_shape) can_support_partial_eq_ord(types, ok_shape) and can_support_partial_eq_ord(types, err_shape)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) -> Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.all(fields, \{ id } -> can_support_partial_eq_ord(types, Types.shape(types, id))) List.all(fields, \{ id } -> can_support_partial_eq_ord(types, Types.shape(types, id)))
@ -1891,7 +1891,7 @@ can_derive_copy = \types, type ->
RocResult(ok_id, err_id) -> RocResult(ok_id, err_id) ->
can_derive_copy(types, Types.shape(types, ok_id)) can_derive_copy(types, Types.shape(types, ok_id))
&& can_derive_copy(types, Types.shape(types, err_id)) and can_derive_copy(types, Types.shape(types, err_id))
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) -> Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.all(fields, \{ id } -> can_derive_copy(types, Types.shape(types, id))) List.all(fields, \{ id } -> can_derive_copy(types, Types.shape(types, id)))
@ -1909,7 +1909,7 @@ cannot_support_default = \types, type ->
TagUnionPayload({ fields: HasClosure(_) }) -> Bool.true TagUnionPayload({ fields: HasClosure(_) }) -> Bool.true
RocDict(key_id, val_id) -> RocDict(key_id, val_id) ->
cannot_support_copy(types, Types.shape(types, key_id)) cannot_support_copy(types, Types.shape(types, key_id))
|| cannot_support_copy(types, Types.shape(types, val_id)) or cannot_support_copy(types, Types.shape(types, val_id))
Struct({ fields: HasClosure(_) }) -> Bool.true Struct({ fields: HasClosure(_) }) -> Bool.true
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) -> Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
@ -1934,7 +1934,7 @@ has_float_help = \types, type, do_not_recurse ->
RocDict(id0, id1) | RocResult(id0, id1) -> RocDict(id0, id1) | RocResult(id0, id1) ->
has_float_help(types, Types.shape(types, id0), do_not_recurse) has_float_help(types, Types.shape(types, id0), do_not_recurse)
|| has_float_help(types, Types.shape(types, id1), do_not_recurse) or has_float_help(types, Types.shape(types, id1), do_not_recurse)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) -> Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.any(fields, \{ id } -> has_float_help(types, Types.shape(types, id), do_not_recurse)) List.any(fields, \{ id } -> has_float_help(types, Types.shape(types, id), do_not_recurse))
@ -2160,7 +2160,7 @@ contains_refcounted_help = \types, type, do_not_recurse ->
RocResult(id0, id1) -> RocResult(id0, id1) ->
contains_refcounted_help(types, Types.shape(types, id0), do_not_recurse) contains_refcounted_help(types, Types.shape(types, id0), do_not_recurse)
|| contains_refcounted_help(types, Types.shape(types, id1), do_not_recurse) or contains_refcounted_help(types, Types.shape(types, id1), do_not_recurse)
Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) -> Struct({ fields: HasNoClosure(fields) }) | TagUnionPayload({ fields: HasNoClosure(fields) }) ->
List.any(fields, \{ id } -> contains_refcounted_help(types, Types.shape(types, id), do_not_recurse)) List.any(fields, \{ id } -> contains_refcounted_help(types, Types.shape(types, id), do_not_recurse))

View file

@ -247,8 +247,6 @@ fn to_expr_report<'a>(
let suggestion = match *op { let suggestion = match *op {
"|" => vec![ "|" => vec![
alloc.reflow("Maybe you want "), alloc.reflow("Maybe you want "),
alloc.parser_suggestion("||"),
alloc.reflow(" or "),
alloc.parser_suggestion("|>"), alloc.parser_suggestion("|>"),
alloc.reflow(" instead?"), alloc.reflow(" instead?"),
], ],

View file

@ -73,7 +73,7 @@ tokens_to_str : List Token -> Str
tokens_to_str = \tokens -> tokens_to_str = \tokens ->
List.walk(tokens, "", \buf, token -> List.walk(tokens, "", \buf, token ->
buf_with_space = buf_with_space =
if Str.is_empty(buf) || token == Literal(",") then if Str.is_empty(buf) or token == Literal(",") then
buf buf
else else
Str.concat(buf, " ") Str.concat(buf, " ")

View file

@ -242,14 +242,14 @@ fromU8 = \r, g, b, a -> @Color (RgbaU8 r g b a)
fromI16 : I16, I16, I16, I16 -> Result Color [OutOfRange] fromI16 : I16, I16, I16, I16 -> Result Color [OutOfRange]
fromI16 = \r, g, b, a -> fromI16 = \r, g, b, a ->
if r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255 then if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255 or a < 0 or a > 255 then
Err OutOfRange Err OutOfRange
else else
Ok (@Color (RgbaU8 (Num.toU8 r) (Num.toU8 g) (Num.toU8 b) (Num.toU8 a))) Ok (@Color (RgbaU8 (Num.toU8 r) (Num.toU8 g) (Num.toU8 b) (Num.toU8 a)))
fromF32 : F32, F32, F32, F32 -> Result Color [OutOfRange] fromF32 : F32, F32, F32, F32 -> Result Color [OutOfRange]
fromF32 = \r, g, b, a -> fromF32 = \r, g, b, a ->
if r < 0.0 || r > 1.0 || g < 0.0 || g > 1.0 || b < 0.0 || b > 1.0 || a < 0.0 || a > 1.0 then if r < 0.0 or r > 1.0 or g < 0.0 or g > 1.0 or b < 0.0 or b > 1.0 or a < 0.0 or a > 1.0 then
Err OutOfRange Err OutOfRange
else else
Ok (@Color (RgbaF32 r g b a)) Ok (@Color (RgbaF32 r g b a))

View file

@ -359,8 +359,8 @@ As a historical note, these stylistic benefits (of `|> Num.sub 1` working as exp
### [Currying and learning curve](#curried-learning-curve) {#curried-learning-curve} ### [Currying and learning curve](#curried-learning-curve) {#curried-learning-curve}
Currying leads to function signatures that look surprising to beginners. For example, in Roc, the Currying leads to function signatures that look surprising to beginners. For example, in Roc, the
[`Bool.and`](https://www.roc-lang.org/builtins/Bool#and) function has the type `Bool, Bool -> Bool`. If Roc were a [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) function has the type `Str, Str -> Str`. If Roc were a
curried language, this function would instead have the type `Bool -> Bool -> Bool`. Since no mainstream programming curried language, this function would instead have the type `Str -> Str -> Str`. Since no mainstream programming
languages today are curried, anyone who knows a mainstream language and is learning their first curried language will languages today are curried, anyone who knows a mainstream language and is learning their first curried language will
require additional explanation about why function types look this way. require additional explanation about why function types look this way.

View file

@ -640,7 +640,7 @@ We refer to whatever comes before a `->` in a `when` expression as a _pattern_
In many programming languages, `true` and `false` are special language keywords that refer to the two [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) values. In Roc, booleans do not get special keywords; instead, they are exposed as the ordinary values `Bool.true` and `Bool.false`. In many programming languages, `true` and `false` are special language keywords that refer to the two [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) values. In Roc, booleans do not get special keywords; instead, they are exposed as the ordinary values `Bool.true` and `Bool.false`.
This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`&&`, `||`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear. This design is partly to keep the number of special keywords in the language smaller, but mainly to suggest how booleans are intended to be used in Roc: for [_boolean logic_](https://en.wikipedia.org/wiki/Boolean_algebra) (`and`, `or`, and so on) as opposed to for data modeling. Tags are the preferred choice for data modeling, and having tag values be more concise than boolean values helps make this preference clear.
As an example of why tags are encouraged for data modeling, in many languages it would be common to write a record like `{ name: "Richard", isAdmin: Bool.true }`, but in Roc it would be preferable to write something like `{ name: "Richard", role: Admin }`. At first, the `role` field might only ever be set to `Admin` or `Normal`, but because the data has been modeled using tags instead of booleans, it's much easier to add other alternatives in the future, like `Guest` or `Moderator` - some of which might also want payloads. As an example of why tags are encouraged for data modeling, in many languages it would be common to write a record like `{ name: "Richard", isAdmin: Bool.true }`, but in Roc it would be preferable to write something like `{ name: "Richard", role: Admin }`. At first, the `role` field might only ever be set to `Admin` or `Normal`, but because the data has been modeled using tags instead of booleans, it's much easier to add other alternatives in the future, like `Guest` or `Moderator` - some of which might also want payloads.
@ -2369,22 +2369,21 @@ Here are various Roc expressions involving operators, and what they desugar to.
| Expression | Desugars To | | Expression | Desugars To |
| ---------------------------- | ---------------------------------------------------------------------------- | | ---------------------------- | ---------------------------------------------------------------------------- |
| `a + b` | `Num.add a b` | | `a + b` | `Num.add(a, b)` |
| `a - b` | `Num.sub a b` | | `a - b` | `Num.sub(a, b)` |
| `a * b` | `Num.mul a b` | | `a * b` | `Num.mul(a, b)` |
| `a / b` | `Num.div a b` | | `a / b` | `Num.div(a, b)` |
| `a // b` | `Num.divTrunc a b` | | `a // b` | `Num.div_trunc(a, b)` |
| `a ^ b` | `Num.pow a b` | | `a ^ b` | `Num.pow(a, b)` |
| `a % b` | `Num.rem a b` | | `a % b` | `Num.rem(a, b)` |
| `-a` | `Num.neg a` | | `-a` | `Num.neg(a)` |
| `a == b` | `Bool.isEq a b` | | `a == b` | `Bool.is_eq(a, b)` |
| `a != b` | `Bool.isNotEq a b` | | `a != b` | `Bool.is_not_eq(a, b)` |
| `a && b` | `Bool.and a b` | | `a and b` | `if a then b else Bool.false` |
| <code>a \|\| b</code> | `Bool.or a b` | | `a or b` | `if a then Bool.true else b` |
| `!a` | `Bool.not a` | | `!a` | `Bool.not(a)` |
| <code>a \|> f</code> | `f a` | | <code>a \|> f</code> | `f(a)` |
| <code>f a b \|> g x y</code> | `g (f a b) x y` | | <code>f a b \|> g x y</code> | `g(f(a, b), x, y)` |
| `f!` | [see example](https://www.roc-lang.org/examples/DesugaringAwait/README.html) |
| `f?` | [see example](https://www.roc-lang.org/examples/DesugaringTry/README.html) | | `f?` | [see example](https://www.roc-lang.org/examples/DesugaringTry/README.html) |
### [Additional Resources](#additional-resources) {#additional-resources} ### [Additional Resources](#additional-resources) {#additional-resources}

View file

@ -111,7 +111,7 @@ view = \page_path_str, html_content ->
"/index.html" -> [id("homepage-main")] "/index.html" -> [id("homepage-main")]
"/tutorial.html" -> [id("tutorial-main"), class("article-layout")] "/tutorial.html" -> [id("tutorial-main"), class("article-layout")]
_ -> _ ->
if Str.starts_with(page_path_str, "/examples/") && page_path_str != "/examples/index.html" then if Str.starts_with(page_path_str, "/examples/") and page_path_str != "/examples/index.html" then
# Individual examples should render wider than articles. # Individual examples should render wider than articles.
# Otherwise the width is unreasonably low for the code blocks, # Otherwise the width is unreasonably low for the code blocks,
# and those pages don't tend to have big paragraphs anyway. # and those pages don't tend to have big paragraphs anyway.