Advertisement
MrPolywhirl

TypeScript Monads!

Jun 12th, 2024 (edited)
567
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
TypeScript 5.96 KB | Software | 0 0
  1. /*
  2.  * =============================================================================================================
  3.  * Monads!
  4.  * -------
  5.  * Adapted from: https://youtu.be/C2w45qRc3aU?t=295
  6.  * =============================================================================================================
  7.  */
  8.  
  9. /**
  10.  * A Loggable type that wraps a value of type T and logs associated with transformations.
  11.  */
  12. type Loggable<T> = { value: T, logs: string[] }
  13.  
  14. /**
  15.  * Wraps a given value in a Loggable object with an empty logs array.
  16.  *
  17.  * @param value - The value to be wrapped.
  18.  * @returns A Loggable object containing the value and an empty logs array.
  19.  */
  20. const toLoggable = <T>(val: T): Loggable<T> => ({ value: val, logs: [] })
  21.  
  22. /**
  23.  * Applies a transformation to a Loggable value of type X, concatenating the logs of the original
  24.  * and transformed values, and returns a new Loggable object of type Y.
  25.  *
  26.  * @param loggable - The initial Loggable value.
  27.  * @param transform - A function that transforms the value of type X and returns a new Loggable object of type Y.
  28.  * @returns A new Loggable object with the transformed value and concatenated logs.
  29.  */
  30. const apply = <X, Y>(loggable: Loggable<X>, transform: (v: X) => Loggable<Y>): Loggable<Y> =>
  31.   ((result) => ({
  32.     value: result.value,
  33.     logs: [...loggable.logs, ...result.logs]
  34.   }))
  35.   (transform(loggable.value))
  36.  
  37. /**
  38.  * Squares a number and logs the transformation.
  39.  *
  40.  * @param n - The number to be squared.
  41.  * @returns A Loggable object containing the squared value and the log of the transformation.
  42.  */
  43. const square = (n: number): Loggable<number> => ({ value: n * n, logs: [`Squared ${n} to get ${n * n}`] })
  44.  
  45. /**
  46.  * Adds one to a number and logs the transformation.
  47.  *
  48.  * @param n - The number to which one is added.
  49.  * @returns A Loggable object containing the incremented value and the log of the transformation.
  50.  */
  51. const addOne = (n: number): Loggable<number> => ({ value: n + 1, logs: [`Added 1 to ${n} to get ${n + 1}`] })
  52.  
  53. // Perform immutable transformations
  54. const a = apply(toLoggable(3), square) // Squared 3 to get 9
  55. const b = apply(a, addOne)             // Added 1 to 9 to get 10
  56. const c = apply(b, square)             // Squared 10 to get 100
  57.  
  58. console.log(`Result: ${c.value}`)
  59. console.log(JSON.stringify(c.logs, null, 2))
  60.  
  61. /*
  62.  * =============================================================================================================
  63.  * Rust-like options
  64.  * =============================================================================================================
  65.  */
  66.  
  67. /**
  68.  * An Option type that can contain a value of type T or be None.
  69.  */
  70. type Option<T> = Some<T> | None
  71.  
  72. /**
  73.  * A Some type that wraps a value of type T.
  74.  */
  75. class Some<T> {
  76.   readonly kind = 'some'
  77.   constructor(public readonly value: T) {}
  78. }
  79.  
  80. /**
  81.  * A None type that represents the absence of a value.
  82.  */
  83. class None {
  84.   readonly kind = 'none'
  85. }
  86.  
  87. /**
  88.  * Helper function to create a Some instance.
  89.  *
  90.  * @param value - The value to wrap in a Some.
  91.  * @returns An Option containing the value.
  92.  */
  93. const some = <T>(val: T): Option<T> => new Some(val)
  94.  
  95. /**
  96.  * Helper function to create a None instance.
  97.  *
  98.  * @returns An Option representing the absence of a value.
  99.  */
  100. const none = (): Option<never> => new None()
  101.  
  102. /**
  103.  * Roman numeral symbols and their corresponding values.
  104.  */
  105. const romanSymbols: [string, number][] = [
  106.   ['M', 1000], ['CM', 900], ['D', 500], ['CD', 400],
  107.   ['C',  100], ['XC',  90], ['L',  50], ['XL',  40],
  108.   ['X',   10], ['IX',   9], ['V',   5], ['IV',   4],
  109.   ['I',    1]
  110. ]
  111.  
  112. /**
  113.  * Converts an integer to a Roman numeral string recursively.
  114.  *
  115.  * @param num - The integer to convert.
  116.  * @param index - The current index in the romanSymbols array (default is 0).
  117.  * @returns A string representing the Roman numeral.
  118.  */
  119. const intToRomanInner = (num: number, index: number = 0): string => {
  120.   if (num === 0) return ''
  121.   const [romanSymbol, value] = romanSymbols[index]
  122.   return num >= value
  123.     ? romanSymbol + intToRomanInner(num - value, index)
  124.     : intToRomanInner(num, index + 1)
  125. }
  126.  
  127. /**
  128.  * Converts an integer to a Roman numeral wrapped in an Option type.
  129.  *
  130.  * @param num - The integer to convert.
  131.  * @returns An Option containing the Roman numeral string or None if input is invalid.
  132.  */
  133. const intToRoman = (num: number): Option<string> => {
  134.   if (isNaN(num) || num < 1) return none()
  135.   return some(intToRomanInner(num))
  136. }
  137.  
  138. /**
  139.  * Converts an integer to a Roman numeral wrapped in a Loggable object.
  140.  *
  141.  * @param n - The integer to convert.
  142.  * @returns A Loggable object containing the Roman numeral and a log of the transformation.
  143.  */
  144. const toRoman = (n: number): Loggable<string> =>
  145.   (result => ({ value: result, logs: [`Converted ${n} to Roman numeral ${result}`] }))
  146.   (handleOption(intToRoman(n), ''))
  147.  
  148. /**
  149.  * Function to handle Option values.
  150.  *
  151.  * @param opt - The Option value to handle.
  152.  * @param defaultValue - The default value to return if the Option is None.
  153.  * @returns The value contained in Some or the default value if None.
  154.  */
  155. const handleOption = <T>(opt: Option<T>, defaultValue: T): T => {
  156.   switch (opt.kind) {
  157.     case 'some': return opt.value
  158.     case 'none': return defaultValue
  159.   }
  160. }
  161.  
  162. // Example usage
  163. const example1 = intToRoman(3549) // Some("MMMDXLIX")
  164. const example2 = intToRoman(0)    // None
  165. const example3 = intToRoman(NaN)  // None
  166.  
  167. // console.log(example1) // Some { kind: 'some', value: 'MMMDXLIX' }
  168. // console.log(example2) // None { kind: 'none' }
  169. // console.log(example3) // None { kind: 'none' }
  170.  
  171. // console.log(handleOption(example1, '')) // "MMMDXLIX"
  172. // console.log(handleOption(example2, '')) // ""
  173. // console.log(handleOption(example3, '')) // ""
  174.  
  175. const x = apply(toLoggable(9), square) // Squared 9 to get 81
  176. const y = apply(x, toRoman)            // Converted 81 to Roman numeral LXXXI
  177.  
  178. console.log(`Result: ${y.value}`)
  179. console.log(JSON.stringify(y.logs, null, 2))
  180.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement