Skip to main content

Day 19 - Conditional data

Skills: 4

Pre-reading: 6.1.1.2, 6.1.2.3, 6.1.3.2, 6.1.3.3

Intro (15 mins)

  • Sometimes, rather than data being composed of different parts, as we saw last time, data comes in multiple, incompatible, forms.
  • For example, a payment system might need to track the source of payment, which could be:
    • Cash, with no further information
    • Check, with the bank account number and routing number (and possibly more information -- account number, check number, etc)
    • Credit Card, with the credit card number (and possibly more information -- expiration date, security code, etc)
  • This doesn't fit into the pattern of structured data, since different alternatives have different, incompatible, components. A security code makes no sense for a check, and a check number has no bearing on a credit card, and all of the extra fields make no sense for cash.
  • Instead, this type of data is conditional -- data that takes the shape of exactly one of several variants.
  • We can define the above using the same data mechanism:
    data PaymentMethod:
    | cash
    | credit(card-number :: String, expiry :: String)
    | checking(bank-account :: String, routing :: String, check-number :: Number)
    end
  • Note that there is one data type PaymentMethod, but three ways of constructing it -- cash, credit, and checking.
  • We create examples of this data similarly to how we did with structured data:
    payment-1 = cash
    payment-2 = credit("1111-2222-3333-4444", "09/26")
    payment-3 = checking("987654321", "111", 55)
  • To use conditional data, however, we can't use the dot notation for field lookup, like with structured data, since given a PaymentMethod, we don't know which variant we have! Instead, we use a new language feature called cases, which allows you to write down what to do in each possible case of the data, creating names for fields in the variants that have fields:
      fun display-payment(p :: PaymentMethod) -> String:
    cases (PaymentMethod) p:
    | cash => "Paid in cash"
    | credit(cn, exp) => "Paid by credit card " + to-string(string-length(cn))
    | checking(acc, rout, num) => "Paid by check from account " + acc
    end
    end
  • We've actually seen this type of data before -- the built-in Option type, which we saw in Day 12, is essentially defined in Pyret as:
data Option:
| some(v)
| none
end

Class Exercises (40 mins)

  • Define a Vehicle data type with three variants: bike (no fields), car (with make and year fields), and truck (with make, year, and capacity fields). Create one example of each variant.
  • Write a function vehicle-age that takes a Vehicle and the current year, and returns the age in years. For bikes, return 0.
  • Define a Grade data type with variants: letter (with a string field for A/B/C/D/F), percent (with a number field), and pass-fail (with a boolean field). Create examples of each.
  • Write a function grade-to-gpa that converts any Grade to a 4.0 scale number. Use: A=4.0, B=3.0, C=2.0, D=1.0, F=0.0; percentages: 90+=4.0, 80-89=3.0, 70-79=2.0, 60-69=1.0, <60=0.0; pass-fail: pass=4.0, fail=0.0.
  • Write a function payment-summary that takes a PaymentMethod and returns a string: "Cash payment", "Card ending in XXXX" (last 4 digits), or "Check #NNNN" (check number).
  • Define a WeatherReport with variants: sunny (temperature only), rainy (temperature and precipitation amount), and snowy (temperature, precipitation, and wind speed). Write a function is-severe that returns true if: rainy with >2 inches, snowy with >30 mph wind, or sunny above 95°F.

Wrap-up (5 mins)

  • Conditional data represents information that can take one of several different forms.
  • Use cases to process conditional data, handling each possible variant.
  • Different variants can have different numbers and types of fields.