I'm writing a "Swift Symbolics" package for fun, similar to how sympy, sagemath, MATLAB (MuPAD) or Wolfram's Mathematica work. With Swift it's the first time I'm experimenting with operator overloading and I'm loving having the ability to write something on the lines of
// calculus
let x = Variable("x")
lim(of: 1/x, for: x, to: 0+) // returns +infinity
lim(of: 1/x, for: x, to: 0-) // returns -infinity
or
// algebra
let ZZ = IntegerRing()
let P = ZZ["x"] // returns the polynomial ring in x over ZZ
let Z2 = ZZ / Ideal(2) // returns the ring of integers modulo 2
using almost 1-1 the mathematical standard notations.
In order to let the user type without thinking too much to specify and place the types every time, I wrote an Expression
type to be used in the following way:
let expression: Expression = 1 + 3 / 4 - 9 ** 0.5
It's just a simple enum
conforming to ExpressibleBy(Integer|Float)Literal
indirect enum Expression: CustomStringConvertible {
case integer(Int)
case real(Double)
case sum(Expression, Expression)
case difference(Expression, Expression)
case product(Expression, Expression)
case division(Expression, Expression)
var description: String {
switch self {
case let .integer(a): return "\(a)"
case let .real(a): return "\(a)"
case let .sum(a, b): return "(\(a) + \(b))"
case let .difference(a, b): return "(\(a) - \(b))"
case let .product(a, b): return "(\(a) * \(b))"
case let .division(a, b): return "(\(a) / \(b))"
}
}
}
// MARK: Literal initialization
extension Expression: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral {
init(integerLiteral value: IntegerLiteralType) {
self = .integer(value)
}
init(floatLiteral value: FloatLiteralType) {
self = .real(value)
}
}
// MARK: Operations
extension Expression {
static func + (lhs: Self, rhs: Self) -> Self { .sum(lhs, rhs) }
static func - (lhs: Self, rhs: Self) -> Self { .difference(lhs, rhs) }
static func * (lhs: Self, rhs: Self) -> Self { .product(lhs, rhs) }
static func / (lhs: Self, rhs: Self) -> Self { .division(lhs, rhs) }
}
// MARK: - Testing
let e: Expression = 3 + 4.1 / 65 * 2 - 12
dump(e)
that allows me to easily get the operations tree. The user just needs to place ": Expression
" after every expression declaration and he'll get more or less the same behavior of sympy.
Unfortunately, with just a couple more operations
let expression: Expression = 3 + 4.1 / 65 * 2 - 12 + 4.1 / 65
the compiler already starts failing to infer the type of every operand
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
even though the only valid combination is the one in which every operand is of type Expression
.
The only way to make the compiler's life easier is to explicitly write that the first integer literal is an Expression
let expression = 3 as Expression + 4.1 / 65 * 2 - 12 + 4.1 / 65
and it won't raise errors even with a thousand of operations involved. Honestly, I don't like how it looks, so my question is: does Swift's type checker always run from left to right?
If a variable has an explicit "resulting" type, shouldn't this information be used to infer the types of the operands in a top-down way instead of a bottom-up one?
I'm actually reading in lib/Sema and I'm currently using both
swiftc -dump-ast file.swift
and
swiftc -Xfrontend -debug-constraints file.swift
to understand what the type checker does under the hood. Unfortunately with the second command I can only get an output if the type checker completes its job. Is there a way to stop the execution and get the partial output?